-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathalert.lua
More file actions
1069 lines (987 loc) · 55.9 KB
/
alert.lua
File metadata and controls
1069 lines (987 loc) · 55.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
--------------------------------------------------------------------------------
-- Functionality similar to JavaScript "alert()" implemented in pure Lua.
--------------------------------------------------------------------------------
-- MODULE: alert
-- VERSION: 4 (2018-11-12)
-- AUTHOR: Egor (egor.skriptunoff(at)gmail.com)
-- This module is released under the MIT License (the same license as Lua itself).
--
-- DESCRIPTION:
-- This module returns the following function:
-- alert(text, title, colors, wait, admit_linebreak_inside_of_a_word)
-- It creates a window with specified text and waits until user closed the window by pressing any key.
--
-- USAGE:
-- Simplest example:
-- local alert = require("alert")
-- alert("Hello")
-- See "example.lua" for more examples.
--
-- REQUIREMENTS:
-- Lua 5.1, Lua 5.2, Lua 5.3, Lua 5.4 or LuaJIT.
-- Lua standard library functions "os.execute()" and "io.popen()" must be non-sandboxed.
-- Supported OS: probably all X11-based *nices, Windows (XP and higher), MacOSX, Cygwin, Wine.
--
-- CHANGELOG:
-- version date description
-- ------- ---------- -----------
-- 4 2018-11-12 Quoted text under Linux is now a bit shorter
-- 3 2016-06-29 Wine detection code updated; now it works correctly with Wine for OSX
-- 2 2016-06-25 Wine support added
-- 1 2016-06-21 First release
-----------------------------------------------------------------------------
local NIL = {} -- this value represents "nil" values inside "initial_config" table
-- Initial values of configurable parameters.
local initial_config = {
-- To preserve module compatibility, don't modify anything in this table.
-- In order to use modified configuration, create specific instance of "alert()" function:
-- local alert = require("alert")(nil, {param_name_1=param_value_1, param_name_2=param_value_2, ...})
-------------------------------------------------------
-- Default values for omitted alert() arguments
-------------------------------------------------------
default_arg_text = NIL, -- [string] or [function returning a string]
default_arg_title = NIL, -- [string] or [function returning a string]
default_arg_colors = NIL, -- [string] or [function returning a string]
default_arg_wait = true, -- [boolean]
default_arg_admit_linebreak_inside_of_a_word = false, -- [boolean]
------------------------------------------------------
-- Parameters concerning to terminal window geometry
------------------------------------------------------
enable_geometry_beautifier = true, -- [boolean]
-- true: set terminal window width and height, center the text in the window, break lines only at word boundaries
-- false: never change size of terminal window, don't center text, don't move linebreaks in the text
-- Terminal window size constraints:
max_width = 80, -- [positive integers]
max_height = 25,
min_width = 44,
min_height = 15,
always_use_maximum_size_of_terminal_window = false, -- [boolean]
-- false: geometry beautifier chooses nice-looking size from range (min_width..max_width)x(min_height..max_height)
-- true: geometry beautifier always sets terminal window size to constant dimensions (max_width)x(max_height)
-- Desired number of unused rows and columns near window borders:
horiz_padding = 4, -- [non-negative integers]
vert_padding = 2,
------------------------------------
-- OS-specific behavior parameters
------------------------------------
-- This parameter is applicable only for CYGWIN.
always_use_cmd_exe_under_cygwin = false, -- [boolean]
-- false: when Cygwin/X is running, terminal emulators are being tried first, failed that CMD.EXE is used.
-- true: when Cygwin/X is running, CMD.EXE is always used (it opens faster, but has limited UTF-8 support).
-- This parameter is applicable only for MacOSX.
always_use_terminal_app_under_macosx = false, -- [boolean]
-- false: when XQuartz is running, *nix terminal emulators are being tried first, failed that Terminal.app is used.
-- true: when XQuartz is running, Terminal.app is always used.
-- This parameter is applicable only for Windows and Wine.
use_windows_native_encoding = false, -- [boolean]
-- false: "text" and "title" arguments are handled as UTF-8 strings whenever possible
-- (if they both are correct UTF-8 strings), otherwise native Windows ANSI codepage is assumed for both of them
-- true: "text" and "title" arguments are always interpreted as strings in native Windows ANSI codepage
-- Please note that Windows ANSI codepage depends on current locale settings, it can be modified by user in
-- "Windows Control Panel" -> "Regional and Language" -> "Language for non-Unicode Programs"
-- This parameter is applicable for all systems except Windows and Wine.
terminal = NIL, -- [any key from "terminals" table]
-- This parameter selects preferred terminal emulator, which will be given highest priority during auto-detection
} -- end of table "initial_config"
-- This is the list of supported terminal emulators.
-- Feel free to add additional terminal emulators that must be here (and send your patch to the module's author).
local terminals = {
-- Description of fields:
-- priority optional number terminal emulators will be checked for being installed in order
-- from highest priority to lowest
-- option_title required string a command line option to set window title (for example, "--title")
-- option_geometry optional string a command line option to set width in columns and height in rows
-- options_misc optional string miscellaneous command line options for this terminal emulator
-- only_8_colors optional boolean if this terminal emulator can display only 8 colors instead of 16
-- option_colors optional string a command line option to set foreground and background colors
-- (if omitted, Esc-sequence will be used to set terminal colors)
-- Next two fields are for terminal emulators:
-- option_command required string an option to provide a shell command to execute (for example, "-e")
-- command_requires_quoting required boolean should shell command be quoted in the command line?
-- Next two fields are for native dialogs, such as "zenity":
-- option_text required string a command line option to pass user text to be displayed
-- text_preprocessor optional function text preprocessing function to implement escaping, etc.
["xfce4-terminal"] = {
priority = -0,
option_geometry = "--geometry=%dx%d", -- actual usage == string.format(option_geometry, my_columns, my_rows)
option_title = "-T", -- actual usage == option_title..[[ 'My Title']]
option_command = "-x", -- actual usage == option_command..[[ command arguments]]
command_requires_quoting = false, -- if true then == option_command..[[ "command arguments"]]
options_misc = "--disable-server --hide-menubar", -- other useful options
},
["mlterm"] = {
priority = -1,
option_geometry = "-g %dx%d", -- actual usage == string.format(option_geometry, my_columns, my_rows)
option_title = "-T", -- actual usage == option_title..[[ 'My Title']]
option_colors = "-f '%s' -b '%s'", -- actual usage == string.format(option_colors, fg#RRGGBB, bg#RRGGBB)
option_command = "-e", -- actual usage == option_command..[[ command arguments]]
command_requires_quoting = false, -- if true then == option_command..[[ "command arguments"]]
options_misc = "-O none", -- other useful options
},
["urxvt"] = { -- rxvt-unicode
priority = -2,
option_geometry = "-g %dx%d", -- actual usage == string.format(option_geometry, my_columns, my_rows)
option_title = "-T", -- actual usage == option_title..[[ 'My Title']]
option_colors = "-fg '%s' -bg '%s'", -- actual usage == string.format(option_colors, fg#RRGGBB, bg#RRGGBB)
option_command = "-e", -- actual usage == option_command..[[ command arguments]]
command_requires_quoting = false, -- if true then == option_command..[[ "command arguments"]]
options_misc = "-sr +sb", -- other useful options
},
["uxterm"] = {
priority = -3,
option_geometry = "-geometry %dx%d", -- actual usage == string.format(option_geometry, my_columns, my_rows)
option_title = "-T", -- actual usage == option_title..[[ 'My Title']]
option_colors = "-fg '%s' -bg '%s'", -- actual usage == string.format(option_colors, fg#RRGGBB, bg#RRGGBB)
option_command = "-e", -- actual usage == option_command..[[ command arguments]]
command_requires_quoting = false, -- if true then == option_command..[[ "command arguments"]]
},
["xterm"] = {
priority = -4,
option_geometry = "-geometry %dx%d", -- actual usage == string.format(option_geometry, my_columns, my_rows)
option_title = "-T", -- actual usage == option_title..[[ 'My Title']]
option_colors = "-fg '%s' -bg '%s'", -- actual usage == string.format(option_colors, fg#RRGGBB, bg#RRGGBB)
option_command = "-e", -- actual usage == option_command..[[ command arguments]]
command_requires_quoting = false, -- if true then == option_command..[[ "command arguments"]]
},
["lxterminal"] = {
priority = -5,
option_geometry = "--geometry=%dx%d", -- actual usage == string.format(option_geometry, my_columns, my_rows)
option_title = "-t", -- actual usage == option_title..[[ 'My Title']]
option_command = "-e", -- actual usage == option_command..[[ command arguments]]
command_requires_quoting = true, -- if true then == option_command..[[ "command arguments"]]
},
["gnome-terminal"] = {
priority = -6,
option_geometry = "--geometry=%dx%d", -- actual usage == string.format(option_geometry, my_columns, my_rows)
option_title = "-t", -- actual usage == option_title..[[ 'My Title']]
option_command = "-x", -- actual usage == option_command..[[ command arguments]]
command_requires_quoting = false, -- if true then == option_command..[[ "command arguments"]]
options_misc = "--disable-factory", -- other useful options
},
["mate-terminal"] = {
priority = -7,
option_geometry = "--geometry=%dx%d", -- actual usage == string.format(option_geometry, my_columns, my_rows)
option_title = "-t", -- actual usage == option_title..[[ 'My Title']]
option_command = "-x", -- actual usage == option_command..[[ command arguments]]
command_requires_quoting = false, -- if true then == option_command..[[ "command arguments"]]
options_misc = "--disable-factory", -- other useful options
},
["sakura"] = {
priority = -8,
option_geometry = "-c %d -r %d", -- actual usage == string.format(option_geometry, my_columns, my_rows)
option_title = "-t", -- actual usage == option_title..[[ 'My Title']]
only_8_colors = true, -- this terminal emulator can display only 8 colors instead of 16
option_command = "-e", -- actual usage == option_command..[[ command arguments]]
command_requires_quoting = true, -- if true then == option_command..[[ "command arguments"]]
},
["roxterm"] = {
priority = -9,
option_geometry = "--geometry=%dx%d", -- actual usage == string.format(option_geometry, my_columns, my_rows)
option_title = "-T", -- actual usage == option_title..[[ 'My Title']]
option_command = "-e", -- actual usage == option_command..[[ command arguments]]
command_requires_quoting = false, -- if true then == option_command..[[ "command arguments"]]
options_misc = "--hide-menubar --separate -n ' '", -- other useful options (how to hide tabbar?)
},
-- The following terminal emulators don't support UTF-8
["mrxvt"] = {
priority = -100,
option_geometry = "-g %dx%d", -- actual usage == string.format(option_geometry, my_columns, my_rows)
option_title = "-T", -- actual usage == option_title..[[ 'My Title']]
option_colors = "-fg '%s' -bg '%s'", -- actual usage == string.format(option_colors, fg#RRGGBB, bg#RRGGBB)
option_command = "-e", -- actual usage == option_command..[[ command arguments]]
command_requires_quoting = false, -- if true then == option_command..[[ "command arguments"]]
options_misc = "+sb -aht +showmenu", -- other useful options
},
["rxvt"] = {
priority = -101,
option_geometry = "-g %dx%d", -- actual usage == string.format(option_geometry, my_columns, my_rows)
option_title = "-T", -- actual usage == option_title..[[ 'My Title']]
option_colors = "-fg '%s' -bg '%s'", -- actual usage == string.format(option_colors, fg#RRGGBB, bg#RRGGBB)
option_command = "-e", -- actual usage == option_command..[[ command arguments]]
command_requires_quoting = false, -- if true then == option_command..[[ "command arguments"]]
options_misc = "-sr +sb", -- other useful options
},
["Eterm"] = {
priority = -102,
option_geometry = "-g %dx%d", -- actual usage == string.format(option_geometry, my_columns, my_rows)
option_title = "-T", -- actual usage == option_title..[[ 'My Title']]
option_colors = "-f '%s' -b '%s'", -- actual usage == string.format(option_colors, fg#RRGGBB, bg#RRGGBB)
option_command = "-e", -- actual usage == option_command..[[ command arguments]]
command_requires_quoting = false, -- if true then == option_command..[[ "command arguments"]]
options_misc = "--scrollbar 0 -P ''", -- other useful options
},
["aterm"] = {
priority = -103,
option_geometry = "-g %dx%d", -- actual usage == string.format(option_geometry, my_columns, my_rows)
option_title = "-T", -- actual usage == option_title..[[ 'My Title']]
option_colors = "-fg '%s' -bg '%s'", -- actual usage == string.format(option_colors, fg#RRGGBB, bg#RRGGBB)
option_command = "-e", -- actual usage == option_command..[[ command arguments]]
command_requires_quoting = false, -- if true then == option_command..[[ "command arguments"]]
options_misc = "-sr +sb", -- other useful options
},
["xvt"] = {
priority = -104,
option_geometry = "-geometry %dx%d", -- actual usage == string.format(option_geometry, my_columns, my_rows)
option_title = "-T", -- actual usage == option_title..[[ 'My Title']]
option_colors = "-fg '%s' -bg '%s'", -- actual usage == string.format(option_colors, fg#RRGGBB, bg#RRGGBB)
option_command = "-e", -- actual usage == option_command..[[ command arguments]]
command_requires_quoting = false, -- if true then == option_command..[[ "command arguments"]]
},
-- the following terminal emulators do support UTF-8, but don't have an option to set its width and height in characters
["evilvte"] = {
priority = -200,
-- option_geometry = -- there is no way to set number of rows and columns for this terminal emulator
option_title = "-T", -- actual usage == option_title..[[ 'My Title']]
option_command = "-e", -- actual usage == option_command..[[ command arguments]]
command_requires_quoting = false, -- if true then == option_command..[[ "command arguments"]]
},
["konsole"] = {
priority = -201,
-- the following "option_geometry" should work but it doesn't
-- (bugs.kde.org/show_bug.cgi?id=345403)
-- option_geometry = "-p TerminalColumns=%d -p TerminalRows=%d",
option_title = "--caption", -- actual usage == option_title..[[ 'My Title']]
-- konsole <-e> option has a problem: it expands all environment variables inside <-e command arguments>
-- despite of protecting them with single quotes, so all $VARs in your text will be forcibly expanded
-- (bugs.kde.org/show_bug.cgi?id=361835)
option_command = "-e", -- actual usage == option_command..[[ command arguments]]
command_requires_quoting = false, -- if true then == option_command..[[ "command arguments"]]
options_misc = "--nofork --hide-tabbar --hide-menubar -p ScrollBarPosition=2", -- other useful options
},
-- native dialogs (they don't have ability to set background color, so colors are not used)
["zenity"] = { -- user should press Enter or Spacebar, or "OK" button with mouse (instead of pressing any key)
priority = -1000,
-- option_geometry = -- zenity does not allow setting number of rows and columns for monospaced font
option_title = "--title", -- actual usage == option_title..[[ 'My Title']]
option_colors = "", -- zenity can't set its window color, so we don't use colors at all
-- option_command = -- "zenity" uses "option_text" instead of "option_command"
option_text = "--text", -- actual usage == option_text..[[ 'My Text']]
text_preprocessor = -- Pango Markup Language requires some escaping
function(text)
return "<tt>"..text:match"^\n*(.-)\n*$":gsub(".", {
["\\"]="\\\\", ["&"]="&", ["<"]="<", [">"]=">", ["\n"]="\\n"
}).."</tt>"
end,
options_misc = "--info --icon-name=", -- other useful options
},
} -- end of table "terminals"
-- all 16 colors available for foreground and background in terminal emulators
local all_colors = {--ANSI_FG ANSI_BG EGA #RRGGBB SYNONYMS
["dark red"] = {"31", "41", "4", "#800000", " maroon"},
["light red"] = {"91", "101", "C", "#FF0000", " red"},
["dark green"] = {"32", "42", "2", "#008000", " green"},
["light green"] = {"92", "102", "A", "#00FF00", " lime"},
["dark yellow"] = {"33", "43", "6", "#808000", " olive"},
["light yellow"] = {"93", "103", "E", "#FFFF00", " yellow"},
["dark blue"] = {"34", "44", "1", "#000080", " navy"},
["light blue"] = {"94", "104", "9", "#0000FF", " blue"},
["dark magenta"] = {"35", "45", "5", "#800080", " purple"},
["light magenta"] = {"95", "105", "D", "#FF00FF", "magenta, fuchsia"},
["dark cyan"] = {"36", "46", "3", "#008080", " teal"},
["light cyan"] = {"96", "106", "B", "#00FFFF", " aqua; cyan"},
["black"] = {"30", "40", "0", "#000000", " afroamerican"},
["dark gray"] = {"90", "100", "8", "#808080", " gray"},
["light gray"] = {"37", "47", "7", "#C0C0C0", " silver"},
["white"] = {"97", "107", "F", "#FFFFFF", " "},
} -- all_colors[color_name] = color_value
-- create "all_colors" table entries for color synonyms
local color_synonyms = {}
local ega_colors = {} -- ega_colors[0..15] = color_value
for name, value in pairs(all_colors) do
ega_colors[tonumber(value[3], 16)] = value
color_synonyms[name:lower():gsub("%W", "")] = value
for syn in value[5]:gmatch"[^,;/]+" do
syn = syn:lower():gsub("%W", "")
if syn ~= "" then
color_synonyms[syn] = value
end
end
end
for name, value in pairs(color_synonyms) do
all_colors[name] = value
all_colors[name:gsub("gray", "grey")] = value
name = name:gsub("^light", "lt"):gsub("^dark", "dk")
all_colors[name] = value
all_colors[name:gsub("gray", "grey")] = value
end
-- calculate best contrast counterparts for all colors
local best_contrast = {} -- best_contrast[color_value] = color_value
for ega_color_id = 0, 15 do
best_contrast[ega_colors[ega_color_id]] =
ega_color_id <= 9 and ega_color_id ~= 7 and all_colors.white or all_colors.black
end
-- function to convert a string "fg/bg" to pair of color values
local function get_color_values(color_names, terminal_is_able_to_display_only_8_colors, avoid_using_default_terminal_colors)
local fg_color_name, bg_color_name = (color_names or "black/silver"):match"^%s*([^/]-)%s*/%s*([^/]-)%s*$"
if not fg_color_name then
error('Wrong "colors" argument for "alert": expected format is "fg_color_name/bg_color_name"', 3)
end
local fg_color_value = all_colors[fg_color_name:gsub("%W", ""):lower()]
local bg_color_value = all_colors[bg_color_name:gsub("%W", ""):lower()]
if fg_color_name ~= "" or bg_color_name ~= "" then
if not (fg_color_value or fg_color_name == "") then
error('"alert" doesn\'t know this color: "'..fg_color_name..'"', 3)
end
if not (bg_color_value or bg_color_name == "") then
error('"alert" doesn\'t know this color: "'..bg_color_name..'"', 3)
end
fg_color_value = fg_color_value or best_contrast[bg_color_value]
bg_color_value = bg_color_value or best_contrast[fg_color_value]
if terminal_is_able_to_display_only_8_colors then
local colors_were_different = fg_color_value ~= bg_color_value
fg_color_value = ega_colors[tonumber(fg_color_value[3], 16) % 8]
bg_color_value = ega_colors[tonumber(bg_color_value[3], 16) % 8]
if colors_were_different and fg_color_value == bg_color_value then
-- pair of fg/bg colors is beyond terminal abilities, default terminal colors will be used
fg_color_value, bg_color_value = nil
end
end
end
if avoid_using_default_terminal_colors then
fg_color_value, bg_color_value = fg_color_value or all_colors.black, bg_color_value or all_colors.white
end
return fg_color_value, bg_color_value
end -- end of function "get_color_values()"
local one_byte_char_pattern = "." -- Lua pattern for characters in Windows ANSI strings
local utf8_char_pattern = "[^\128-\191][\128-\191]*" -- Lua pattern for characters in UTF-8 strings
local function geometry_beautifier(
cfg, -- configuration of current alert() instance
text, -- text which layout should be beautified (text centering and padding, nice line splitting)
char_pattern, -- Lua pattern for matching one symbol in the text
early_line_overflow, -- true: cursor jumps to next line when previous line has been filled but not yet overflowed
admit_linebreak_inside_of_a_word, -- true: disable inserting additional LF in the safe locations of the text
exact_geometry_is_unknown -- true (or number): we have no control over width and height of the terminal window
) -- Three values returned:
-- text (all newlines CR/CRLF/LF are converted to LF, last line is not terminated by LF)
-- chosen terminal width (nil if geometry beautifier is disabled)
-- chosen terminal height (nil if geometry beautifier is disabled)
text = (text or ""):gsub("\r\n?", "\n"):gsub("%z","\n"):gsub("[^\n]$", "%0\n")
local width, height
if cfg.enable_geometry_beautifier then
local min_width = math.max(12, cfg.min_width )
local min_height = math.max( 3, cfg.min_height)
local max_width = math.max(12, cfg.max_width )
local max_height = math.max( 3, cfg.max_height)
if exact_geometry_is_unknown then
-- we have no control over width and height of the terminal window, but we assume
-- that terminal window has exactly 80 columns and at least 23 rows (this is very probable)
max_width = 80
max_height = type(exact_geometry_is_unknown) == "number" and exact_geometry_is_unknown or 23
end
local pos, left_cut, right_cut = 0, math.huge, 0
local line_no, top_cut, bottom_cut = 0, math.huge, 0
local line_is_not_empty
text = text:gsub(char_pattern,
function(c)
if c == "\n" then
pos = 0
if line_is_not_empty then
line_is_not_empty = false
top_cut = math.min(top_cut, line_no)
bottom_cut = math.max(bottom_cut, line_no + 1)
end
line_no = line_no + 1
elseif c == "\t" then
local delta = 8 - pos % 8
pos = pos + delta
return (' '):rep(delta)
else
if c:find"%S" then
left_cut = math.min(left_cut, pos)
right_cut = math.max(right_cut, pos + 1)
line_is_not_empty = true
end
pos = pos + 1
end
end
)
left_cut = math.min(left_cut, right_cut)
width = math.min(max_width, math.max(right_cut - left_cut + 2*cfg.horiz_padding,
(cfg.always_use_maximum_size_of_terminal_window or exact_geometry_is_unknown) and max_width or min_width))
local line_length_limit =
not admit_linebreak_inside_of_a_word and right_cut - left_cut > max_width and max_width - 2*cfg.horiz_padding
local left_indent =
(" "):rep(line_length_limit and cfg.horiz_padding or math.max(math.floor((width - right_cut + left_cut)/2), 0))
top_cut = math.min(top_cut, bottom_cut)
local actual_height, new_text, line_no = 0, "", 0
for line in text:gmatch"(.-)\n" do
if line_no >= top_cut and line_no < bottom_cut then
local prefix, prefix_len, new_line, new_line_len, pos, tail_of_spaces = "", 0, "", 0, 0, ""
local punctuation, remove_leading_spaces
for c in (line.." "):gmatch(char_pattern) do
if pos >= left_cut then
if line_length_limit and ( -- There are two kinds of locations to split a line nicely:
punctuation and (c:find"%w" or c:byte() > 127) -- 1) alphanumeric after punctuation
or tail_of_spaces == "" and pos > left_cut and not c:find"%S" -- 2) space after non-space
) then
if prefix_len + new_line_len > line_length_limit and prefix ~= ""
and #new_line:match"%S.*" < line_length_limit/3 then
new_text = new_text..left_indent..prefix.."\n"
actual_height = actual_height + 1
prefix, prefix_len, remove_leading_spaces = "", 0, true
end
repeat
if new_line == "" then
local length_in_bytes = 0
for _ = 1, line_length_limit do
length_in_bytes = select(2, prefix:find(char_pattern, length_in_bytes + 1))
end
local next_line = (left_indent..prefix:sub(1, length_in_bytes)):match".*%S"
remove_leading_spaces = next_line ~= nil
new_text = new_text..(next_line or "").."\n"
actual_height = actual_height + 1
prefix, prefix_len = prefix:sub(1 + length_in_bytes), prefix_len - line_length_limit
end
prefix, new_line, prefix_len, new_line_len = prefix..new_line, "", prefix_len + new_line_len, 0
local spaces_at_the_beginning = #prefix:match"%s*"
if remove_leading_spaces and spaces_at_the_beginning > 0 then
prefix, prefix_len = prefix:sub(1 + spaces_at_the_beginning), prefix_len - spaces_at_the_beginning
end
until prefix_len <= line_length_limit
end
if c:find"%S" then
new_line = new_line..tail_of_spaces..c
new_line_len = new_line_len + #tail_of_spaces + 1
tail_of_spaces = ""
else
tail_of_spaces = tail_of_spaces..c
end
punctuation = (",;"):find(c, 1, true) -- dot was excluded to avoid splitting of numeric literals
end
pos = pos + 1
end
if line_length_limit then
new_line, new_line_len = prefix, prefix_len
end
new_text = new_text..(new_line == "" and "" or left_indent)..new_line.."\n"
actual_height = actual_height + math.max(math.ceil(new_line_len/width), 1)
end
line_no = line_no + 1
end
height = math.min(max_height, math.max(actual_height + 2*cfg.vert_padding,
(cfg.always_use_maximum_size_of_terminal_window or exact_geometry_is_unknown) and max_height or min_height))
local top_indent_size = math.floor((height - actual_height)/2)
text = ("\n"):rep(math.max(top_indent_size, exact_geometry_is_unknown and cfg.vert_padding or 0))
..new_text..("\n"):rep(height - actual_height - top_indent_size - 1)
if early_line_overflow then
text = text:gsub("(.-)\n",
function(line)
return line ~= "" and select(2, line:gsub(char_pattern, "")) % width == 0 and line
end
)
end
end
return text:gsub("\n$", ""), width, height
end -- end of function "geometry_beautifier()"
-- the must-have system functions for this module:
local os_execute, io_popen, os_getenv = os.execute, io.popen, os.getenv
-- the following functions are required only under Wine and CJK Windows
local io_open, os_remove = io.open, os.remove -- they are needed to create and delete temporary file
local function get_output(command, format, binary_mode)
local pipe = io_popen(command, binary_mode and "rb" or "r")
local result = pipe:read(format or "*a")
pipe:close()
return result
end
local test_echo, env_var_os, cmd_echo, system_name, xinit_proc_cnt, wait_key_method_code
local tempfolder, tempfileid, sbcs, mbcs, ansi_to_utf16, utf16_to_ansi, utf16_to_oem, utf8_to_sbcs, tempfilespec
local locale_dependent_chars
local display_CLOSE_THIS_WINDOW_message = true
local function create_function_alert(cfg) -- function constructor
if not (os_execute and io_popen) then
error('"alert" requires "os.execute" and "io.popen"', 3)
end
test_echo = test_echo or get_output"echo Test" -- command "echo Test" should work on any OS
if not test_echo:match"^Test" then
error('"alert" requires non-sandboxed "os.execute" and "io.popen"', 3)
end
env_var_os = env_var_os or os_getenv"oS" or ""
-- "oS" is not a typo. It prevents Cygwin from being incorrectly identified as Windows.
-- Cygwin inherits Windows environment variables, but treats them as if they were case-sensitive.
if env_var_os:find"^Windows" then
----------------------------------------------------------------------------------------------------
-- Windows or Wine
----------------------------------------------------------------------------------------------------
local function get_binary_output(command)
return get_output(command, nil, true)
end
local function create_binary_file(filename, content)
local file = assert(io_open(filename, "wb"))
file:write(content)
file:close()
end
local function getwindowstempfilespec()
if not tempfolder then
tempfolder = assert(os_getenv"TMP" or os_getenv"TEMP", "%TMP% environment variable is not set")
tempfileid = os.time() * 3456793 -- tempfileid is an integer number in the range 0..(2^53)-1
-- We want to make temporary file name different for every run of the program
-- %random% is 15-bit random integer generated by OS
-- %time% is current time with 0.01 seconds precision on Windows (one-minute precision on Wine)
-- tostring{} contains table's address inside the heap, heap location is changed on every run due to ASLR
;(tostring{}..get_output"echo %random%%time%%date%"):gsub("..",
function(s)
tempfileid = tempfileid % 68719476736 * 126611
+ math.floor(tempfileid/68719476736) * 505231
+ s:byte() * 3083 + s:byte(2)
end)
end
tempfileid = tempfileid + 1
return tempfolder..("\\alert_%.f.tmp"):format(tempfileid)
end
if not locale_dependent_chars then
locale_dependent_chars = {}
for code = 128, 255 do
locale_dependent_chars[code - 127] = string.char(code)
end
locale_dependent_chars = table.concat(locale_dependent_chars)
end
local function is_utf8(str)
local is_ascii7 = true
for c in str:gmatch"[^\128-\191]?[\128-\191]*" do
local len, first = #c, c:byte()
if len > 4 or len == 4 and not (first >= 0xF0 and first < 0xF5)
or len == 3 and not (first >= 0xE0 and first < 0xF0)
or len == 2 and not (first >= 0xC2 and first < 0xE0)
or len == 1 and not (first < 0x80) then
return false
end
is_ascii7 = is_ascii7 and len < 2
end
return true, is_ascii7
end
local function convert_char_utf8_to_utf16(c)
local c1, c2, c3, c4 = c:byte(1, 4)
local unicode
if c4 then -- [1111 0xxx] [10xx xxxx] [10xx xxxx] [10xx xxxx]
unicode = ((c1 % 8 * 64 + c2 % 64) * 64 + c3 % 64) * 64 + c4 % 64
elseif c3 then -- [1110 xxxx] [10xx xxxx] [10xx xxxx]
unicode = (c1 % 16 * 64 + c2 % 64) * 64 + c3 % 64
elseif c2 then -- [110x xxxx] [10xx xxxx]
unicode = c1 % 32 * 64 + c2 % 64
else -- [0xxx xxxx]
unicode = c1
end
if unicode < 0x10000 then
return string.char(unicode % 256, math.floor(unicode/256))
else -- make surrogate pair for unicode code points above 0xFFFF
local unicode1 = 0xD800 + math.floor((unicode - 0x10000)/0x400) % 0x400
local unicode2 = 0xDC00 + (unicode - 0x10000) % 0x400
return string.char(unicode1 % 256, math.floor(unicode1/256),
unicode2 % 256, math.floor(unicode2/256))
end
end
local function convert_string_utf8_to_utf16(str, with_bom)
return (with_bom and "\255\254" or "")..str:gsub(utf8_char_pattern, convert_char_utf8_to_utf16)
end
-- Wine and Windows parse command line differently, we use it for Wine detection
cmd_echo = cmd_echo or get_output'cmd /d/c "echo "^^""'
local is_wine = cmd_echo:find"%^^"
if not is_wine then
----------------------------------------------------------------------------------------------------
-- Invocation of CMD.EXE on WINDOWS
----------------------------------------------------------------------------------------------------
local function convert_string_utf8_to_oem(str, filename)
-- convert UTF-8 to UTF-16LE with BOM
create_binary_file(filename, convert_string_utf8_to_utf16(str.."#", true)) -- create temporary file
-- convert UTF-16LE to OEM
local converted = assert(get_binary_output('type "'..filename..'"'):match"^(.*)#")
assert(os_remove(filename)) -- delete temporary file
return converted
end
local function to_native(str)
if not (sbcs or mbcs) then
local converted = get_binary_output("cmd /u/d/c echo("..locale_dependent_chars.."$")
if converted:sub(257, 258) == "$\0" then
-- Windows native codepage is Single-Byte Character Set
sbcs = true
-- create table for fast conversion of UTF-8 characters to Single-Byte Character Set
utf8_to_sbcs = {}
for code = 128, 255 do
local low, high = converted:byte(2*code - 255, 2*code - 254)
local unicode = high * 256 + low
if unicode > 0x7FF then -- [1110 xxxx] [10xx xxxx] [10xx xxxx]
utf8_to_sbcs[string.char(
0xE0 + math.floor(unicode/4096),
0x80 + math.floor(unicode/64) % 64,
0x80 + unicode % 64)] = string.char(code)
elseif unicode > 0x7F then -- [110x xxxx] [10xx xxxx]
utf8_to_sbcs[string.char(
0xC0 + math.floor(unicode/64),
0x80 + unicode % 64)] = string.char(code)
end
end
else
-- Windows native codepage is Multi-Byte Character Set
mbcs = true
tempfilespec = getwindowstempfilespec() -- temporary file for converting unicode strings to MBCS
end
end
if sbcs then
-- UTF-8 to SBCS
return (str:gsub(utf8_char_pattern, function(c) return #c > 1 and (utf8_to_sbcs[c] or "?") end))
else
-- UTF-8 to MBCS
-- on multibyte Windows encodings ANSI codepage is the same as OEM codepage
return convert_string_utf8_to_oem(str, tempfilespec)
end
end
return function (text, title, colors, wait, admit_linebreak_inside_of_a_word)
text, title = text or "", title or "Press any key"
if not cfg.use_windows_native_encoding then
local text_is_utf8, text_is_ascii7 = is_utf8(text)
local title_is_utf8, title_is_ascii7 = is_utf8(title)
if text_is_utf8 and title_is_utf8 then
text = text_is_ascii7 and text or to_native(text)
title = title_is_ascii7 and title or to_native(title)
end
end
local text, width, height =
geometry_beautifier(cfg, text, one_byte_char_pattern, true, admit_linebreak_inside_of_a_word)
local fg, bg = get_color_values(colors)
local lines = {}
local function add_line(prefix, line)
table.insert(lines, prefix..line:gsub(".", {
["("]="^(", [")"]="^)", ["&"]="^&", ["|"]="^|", ["^"]="^^",
[">"]="^>", ["<"]="^<", ["%"]="%^<", ['"']="%^>"
}))
end
title = title:sub(1,200):match"%C+" or ""
-- the following check is needed to avoid invocation of "title /?"
if title:find'["%%]' and not title:find"/[%s,;=]*%?" then
add_line("title ", title)
title = ""
end
for line in (text.."\n"):gmatch"(.-)\n" do
add_line("echo(", line)
end
os_execute(
'"start "'..title:gsub(".", {['"']="'", ["%"]=" % "})..'" '
..(wait and "/wait " or "")
..'cmd /d/c"'
..(width and "mode "..width..","..height.."&" or "")
..(fg and "color "..bg[3]..fg[3].."&" or "")
..'for /f "tokens=1-3delims=_" %^< in ("%_"_"")do @('..table.concat(lines, "&")..")&"
..'pause>nul:""'
)
end
end
----------------------------------------------------------------------------------------------------
-- Invocation of CMD.EXE on Wine
----------------------------------------------------------------------------------------------------
local function initialize_convertor(filename)
local converted_ansi = get_binary_output("cmd /u/d/c echo "..locale_dependent_chars.."$")
if converted_ansi:sub(257, 258) == "$\0" then
-- Wine codepage is Single-Byte Character Set
sbcs = true
-- create tables for fast conversion UTF-16 to/from Single-Byte Character Set
ansi_to_utf16 = {} -- ansi_to_utf16[ansi char] = utf-16 char
utf16_to_ansi = {} -- utf16_to_ansi[utf-16 char] = ansi char
utf16_to_oem = {} -- utf16_to_oem[utf-16 char] = oem char
create_binary_file(filename, locale_dependent_chars)
local converted_oem = get_binary_output("cmd /u/d/c type "..filename)
for code = 0, 255 do
local c = string.char(code)
local w_ansi = code < 128 and c.."\0" or converted_ansi:sub(2*code - 255, 2*code - 254)
if code < 128 or w_ansi:byte(2) * 256 + w_ansi:byte() > 0x7F then
ansi_to_utf16[c] = w_ansi
utf16_to_ansi[w_ansi] = c
end
local w_oem = code < 128 and w_ansi or converted_oem:sub(2*code - 255, 2*code - 254)
if code < 128 or w_oem:byte(2) * 256 + w_oem:byte() > 0x7F then
utf16_to_oem[w_oem] = c
end
end
else
-- Wine codepage is Multi-Byte Character Set
mbcs = true
end
end
return function (text, title, colors, wait, admit_linebreak_inside_of_a_word)
text, title = text or "", (title or "Press any key"):sub(1,200):match"%C+" or ""
local text_is_utf8, text_is_ascii7 = is_utf8(text)
local title_is_utf8, title_is_ascii7 = is_utf8(title)
local char_pattern =
(not cfg.use_windows_native_encoding and text_is_utf8 and title_is_utf8)
and utf8_char_pattern
or one_byte_char_pattern
local text = geometry_beautifier(cfg, text, char_pattern, true, admit_linebreak_inside_of_a_word, 25)
local fg, bg = get_color_values(colors)
local tempfilename = getwindowstempfilespec() -- temporary file for saving text
-- convert title to ANSI codepage
if not title_is_ascii7 and char_pattern == utf8_char_pattern then
if not (sbcs or mbcs) then
initialize_convertor(tempfilename)
end
if sbcs then
title = convert_string_utf8_to_utf16(title)
:gsub("..", function(w) return utf16_to_ansi[w] or "?" end)
end
end
-- convert text to OEM codepage and save to temporary file
text = (text.."\n"):gsub("\n", "\r\n")
if not text_is_ascii7 then
if not (sbcs or mbcs) then
initialize_convertor(tempfilename)
end
if sbcs then
if char_pattern == utf8_char_pattern then
text = convert_string_utf8_to_utf16(text)
else
text = text:gsub(".", ansi_to_utf16)
end
text = text:gsub("..", function(w) return utf16_to_oem[w] or "?" end)
end
end
create_binary_file(tempfilename, text)
os_execute(
"start "
..(wait and "/wait " or "")
..'cmd /d/c "'
..(fg and "color "..bg[3]..fg[3].."&" or "")
.."title "..title:gsub("/%?", "/ ?") -- to avoid invocation of "title /?"
:gsub(".", {["&"]="^&", ["|"]="^|", ["^"]="^^", [">"]="^>", ["<"]="^<", ["%"]=" % ", ['"']="'"})
.."&type "..tempfilename
.."&del "..tempfilename.." 2>nul:"
..'&pause>nul:"'
)
end
end
----------------------------------------------------------------------------------------------------
-- *NIX
----------------------------------------------------------------------------------------------------
local function q(text) -- quoting under *nix shells
if text == "" then
text = '""'
elseif text:match"%W" then
local t = {}
for s in (text.."'"):gmatch"(.-)'" do
t[#t + 1] = s:match"%W" and "'"..s.."'" or s
end
text = table.concat(t, "\\'")
end
return text
end
system_name = system_name or get_output"uname":match"%C+"
local is_macosx = system_name == "Darwin"
local is_cygwin = system_name:find"^CYGWIN" or system_name:find"^MINGW" or system_name:find"^MSYS"
local xless_system =
is_macosx and cfg.always_use_terminal_app_under_macosx
or is_cygwin and cfg.always_use_cmd_exe_under_cygwin
if not xless_system and (is_macosx or is_cygwin) then
xinit_proc_cnt = xinit_proc_cnt or get_output("(ps ax|grep /bin/xinit|grep -c -v grep)2>/dev/null", "*n") or 0
xless_system = xinit_proc_cnt == 0
end
if not xless_system then
----------------------------------------------------------------------------------------------------
-- Auto-detection of terminal emulator on *nix
----------------------------------------------------------------------------------------------------
local function get_terminal_priority(terminal)
return terminal == cfg.terminal and math.huge or terminals[terminal].priority or -math.huge
end
local terminal_names = {}
for terminal in pairs(terminals) do
table.insert(terminal_names, terminal)
end
table.sort(terminal_names,
function(a, b)
local pr_a, pr_b = get_terminal_priority(a), get_terminal_priority(b)
return pr_a < pr_b or pr_a == pr_b and a < b
end
)
local command, delta = "exit 0", 70
for k, terminal in ipairs(terminal_names) do
command = "command -v "..terminal.."&&exit "..k+delta.."||"..command
end
local function run_quietly_and_get_exit_code(shell_command)
return get_output("("..shell_command..")>/dev/null 2>&1;echo $?", "*n") or -1
end
local terminal = terminal_names[run_quietly_and_get_exit_code(command) - delta]
if terminal then
----------------------------------------------------------------------------------------------------
-- Invocation of terminal emulator on *nix
----------------------------------------------------------------------------------------------------
-- choosing a method of waiting for user pressed a key
local mc
if terminals[terminal].option_command then
wait_key_method_code = wait_key_method_code or
run_quietly_and_get_exit_code"command -v dd&&command -v stty&&exit 69||command -v bash&&exit 68||exit 0"
mc = wait_key_method_code
end
local method = ({
[68] = {default_title = "Press any key",
shell = "bash",
wait_a_key = "read -rsn 1"},
[69] = {default_title = "Press any key",
shell = "sh",
wait_a_key = "stty -echo raw;dd bs=1 count=1 >/dev/null 2>&1;stty sane"}
})[mc] or {default_title = "Press Enter",
shell = "sh",
wait_a_key = "read a"}
local function nop(...) return ... end
local exact_geometry_is_unknown = not terminals[terminal].option_geometry
return function (text, title, colors, wait, admit_linebreak_inside_of_a_word)
title = title or method.default_title
local text, width, height = geometry_beautifier(
cfg, text, utf8_char_pattern, false, admit_linebreak_inside_of_a_word, exact_geometry_is_unknown)
local fg, bg = get_color_values(colors, terminals[terminal].only_8_colors)
if fg and not terminals[terminal].option_colors then
text = "\27["..fg[1]..";"..bg[2].."m\27[J"..text
end
os_execute(
((is_cygwin or is_macosx) and "DISPLAY=:0 " or "")
..terminal.." "
..(terminals[terminal].options_misc or "").." "
..(fg
and terminals[terminal].option_colors and terminals[terminal].option_colors:format(fg[4], bg[4]).." "
or "")
..(width
and (terminals[terminal].option_geometry or ""):format(width, height).." "
or "")
..terminals[terminal].option_title.." "..q(title).." "
..(terminals[terminal].option_command
and
terminals[terminal].option_command.." "..
(terminals[terminal].command_requires_quoting and q or nop)(
method.shell.." -c "..q("echo "..q(text)..";"..method.wait_a_key)
)..">/dev/null 2>&1"
or
terminals[terminal].option_text.." "..q((terminals[terminal].text_preprocessor or nop)(text))
)
..(wait and "" or " &")
)
end
end
end
if is_macosx then
----------------------------------------------------------------------------------------------------
-- Invocation of Terminal.app on MacOSX
----------------------------------------------------------------------------------------------------
local function q_as(text) -- quoting under AppleScript
return '"'..text:gsub('[\\"]', "\\%0")..'"'
end
return function (text, title, colors, wait, admit_linebreak_inside_of_a_word)
title = title or "Press any key"
local text, width, height =
geometry_beautifier(cfg, text, utf8_char_pattern, false, admit_linebreak_inside_of_a_word)
local fg, bg = get_color_values(colors, nil, true)
local r, g, b = bg[4]:match"(%x%x)(%x%x)(%x%x)"
local rgb = "{"..tonumber(r..r, 16)..","..tonumber(g..g, 16)..","..tonumber(b..b, 16).."}"
os_execute( -- "shell command" nested into 'AppleScript' nested into "shell command"
"osascript -e "..q(
'set w to 1\n' -- 1 second (increase it when running Mac OS X as VM guest)
.. 'if app "Terminal" is running then set w to 0\n' -- 0 seconds
.. 'do shell script "open -a Terminal ."\n'
.. 'delay w\n' -- Terminal.app may take about a second to start, this delay happens only once
.. 'tell app "Terminal"\n'
.. 'tell window 1\n'
.. (width and string.format(
'set number of columns to %d\n'
.. 'set number of rows to %d\n', width, height) or '')
.. 'set normal text color to '..rgb..'\n'
.. 'set background color to '..rgb..'\n'
.. 'set custom title to '..q_as(title)..'\n'
.. 'do script '..q_as(
"echo $'\\ec\\e['"..q(fg[1].."m"..text)..";read -rsn 1;echo $'\\e[H\\e[J"
.. (wait and display_CLOSE_THIS_WINDOW_message and "\n"
.. " PLEASE CLOSE THIS WINDOW TO CONTINUE\n\n" -- this will be displayed only once
.. "The following profile setting may be useful:\n"
.. "Terminal -> Preferences -> Settings -> Shell\n"
.. "When the shell exits: Close the window" or "").."\\e[0m';exit"
)..' in it\n'
.. (wait and
'set w to its id\n'
.. 'end\n'
.. 'repeat while id of every window contains w\n'
.. 'delay 0.1\n' or '')
.. 'end\n'
.. 'end\n'
)..">/dev/null 2>&1"
)
display_CLOSE_THIS_WINDOW_message = not wait and display_CLOSE_THIS_WINDOW_message
end
end
if is_cygwin then
----------------------------------------------------------------------------------------------------
-- Invocation of CMD.EXE on CYGWIN
----------------------------------------------------------------------------------------------------
return function (text, title, colors, wait, admit_linebreak_inside_of_a_word)
local text, width, height =
geometry_beautifier(cfg, text, utf8_char_pattern, true, admit_linebreak_inside_of_a_word)
local fg, bg = get_color_values(colors)
local lines = {}
local function add_line(prefix, line)
table.insert(lines, prefix..line:gsub(".", {
["("]="^(", [")"]="^)", ["&"]="^&", ["|"]="^|", ["^"]="^^",
[">"]="^>", ["<"]="^<", ["%"]="%^<", ['"']="%^>", ["'"]="'\\''"
}))
end
title = (title or "Press any key"):sub(1,200):match"%C+" or ""
if title:find'["%%]' and not title:find"/[%s,;=]*%?" then
add_line("title ", title)
title = ""
end
for line in (text.."\n"):gmatch"(.-)\n" do
add_line("echo(", line)