trial.terminal 110/1329(8%) line coverage

      
10
20
30
40
50
60
70
80
90
100
110
120
130
140
150
160
170
180
190
200
210
220
230
240
250
260
270
280
290
300
310
320
330
340
350
360
370
380
390
400
410
420
430
440
450
460
470
480
490
500
510
520
530
540
550
560
570
580
590
600
610
620
630
640
650
660
670
680
690
700
710
720
730
740
750
760
770
780
790
800
810
820
830
840
850
860
870
880
890
900
910
920
930
940
950
960
970
980
990
1000
1010
1020
1030
1040
1050
1060
1070
1080
1090
1100
1110
1120
1130
1140
1150
1160
1170
1180
1190
1200
1210
1220
1230
1240
1250
1260
1270
1280
1290
1300
1310
1320
1330
1340
1350
1360
1370
1380
1390
1400
1410
1420
1430
1440
1450
1460
1470
1480
1490
1500
1510
1520
1530
1540
1550
1560
1570
1580
1590
1600
1610
1620
1630
1640
1650
1660
1670
1680
1690
1700
1710
1720
1730
1740
1750
1760
1770
1780
1790
1800
1810
1820
1830
1840
1850
1860
1870
1880
1890
1900
1910
1920
1930
1940
1950
1960
1970
1980
1990
2000
2010
2020
2030
2040
2050
2060
2070
2080
2090
2100
2110
2120
2130
2140
2150
2160
2170
2180
2190
2200
2210
2220
2230
2240
2250
2260
2270
2280
2290
2300
2310
2320
2330
2340
2350
2360
2370
2380
2390
2400
2410
2420
2430
2440
2450
2460
2470
2480
2490
2500
2510
2520
2530
2540
2550
2560
2570
2580
2590
2600
2610
2620
2630
2640
2650
2660
2670
2680
2690
2700
2710
2720
2730
2740
2750
2760
2770
2780
2790
2800
2810
2820
2830
2840
2850
2860
2870
2880
2890
2900
2910
2920
2930
2940
2950
2960
2970
2980
2990
3000
3010
3020
3030
3040
3050
3060
3070
3080
3090
3100
3110
3120
3130
3140
3150
3160
3170
3180
3190
3200
3211
32212
3233
3240
3250
3261
3270
3280
3290
3300
3310
3320
3330
3340
3350
3360
3370
3380
3390
3400
3410
3420
3430
3440
3451
3461
3470
3481
3490
3501
35133
35215
3530
35418
35518
35618
35717
3580
359204
36050
3610
36218
3630
3640
3651
366143
36715
36815
3690
370128
37110
372118
373118
374100
375118
3760
377118
37818
3790
3800
3811
382432
383143
3840
3850
3860
3870
3880
3890
3900
3910
3920
3930
3940
3951
3961
3970
3981
3991
4001
4010
4020
4030
4040
4050
4060
4070
4080
4090
4100
4110
4120
4130
4140
4150
4160
4170
4180
4190
4200
4211
4221
4231
4240
4250
4261
4271
4280
4293
4300
4310
4320
4330
4340
4350
4360
4370
4380
4390
4400
4410
4420
4430
4440
4450
4460
4470
4480
4490
4500
4510
4520
4530
4540
4550
4560
4570
4580
4590
4600
4610
4620
4630
4640
4650
4660
4670
4680
4690
4700
4710
4720
4730
4740
4750
4760
4770
4780
4790
4800
4810
4820
4830
4840
4850
4860
4870
4880
4890
4900
4910
4920
4930
4940
4950
4960
4970
4980
4990
5000
5010
5020
5030
5040
5050
5060
5070
5080
5090
5100
5110
5120
5130
5140
5150
5160
5170
5180
5190
5200
5210
5220
5230
5240
5250
5260
5270
5280
5290
5300
5310
5320
5330
5340
5350
5360
5370
5380
5390
5400
5410
5420
5430
5440
5450
5460
5470
5480
5490
5500
5510
5520
5530
5540
5550
5560
5570
5580
5590
5600
5610
5620
5630
5640
5650
5660
5670
5680
5690
5700
5710
5720
5730
5740
5750
5760
5770
5780
5790
5800
5810
5820
5830
5840
5850
5860
5870
5880
5890
5900
5910
5920
5930
5940
5950
5960
5970
5980
5990
6000
6010
6020
6030
6040
6051
6061
6071
6081
6091
6100
6111
6120
6131
6140
6150
6160
6170
6181
6190
6200
6210
6220
6230
6241
6250
6260
6270
6280
6290
6300
6310
6320
6330
6340
6350
6360
6370
6380
6390
6400
6410
6420
6430
6440
6450
6460
6470
6480
6490
6500
6510
6520
6530
6540
6550
6560
6570
6580
6590
6600
6610
6620
6630
6640
6650
6660
6670
6680
6690
6700
6710
6720
6730
6740
6750
6760
6770
6780
6790
6800
6810
6820
6830
6840
6850
6860
6870
6880
6890
6900
6910
6920
6930
6940
6950
6960
6970
6980
6990
7000
7010
7020
7030
7040
7050
7060
7070
7080
7090
7100
7110
7120
7130
7140
7150
7160
7170
7180
7190
7200
7210
7220
7230
7240
7250
7260
7270
7280
7290
7300
7310
7320
7330
7340
7350
7360
7370
7380
7390
7400
7410
7420
7430
7440
7450
7460
7470
7480
7490
7500
7510
7520
7530
7540
7550
7560
7570
7580
7590
7600
7610
7620
7630
7640
7650
7660
7670
7680
769918
7700
7710
7720
7730
7740
7750
7760
7770
7780
7790
7800
7810
7820
7830
7840
7850
7860
7870
7880
7890
7900
7910
7920
7930
7940
7950
7960
7970
7980
7990
8000
8010
8020
8030
8040
8050
806918
807918
8080
809918
8100
811918
812918
8130
8140
8150
8162754
817918
8181836
8190
8200
821918
8220
8230
8240
8250
8260
827918
828918
829918
8300
8310
8320
8330
8340
8350
8360
8370
8380
8390
8400
8410
8420
8430
8440
8450
8460
8470
8480
8490
8500
8510
8520
8530
8540
855918
8560
857918
858918
859918
860918
8610
8620
8630
8640
8650
8660
8670
8680
8690
8700
8710
8721
8730
8740
8750
8760
8770
8780
8790
8800
8810
8820
8830
8840
8850
8860
8870
8880
8890
8900
8910
8920
8930
8940
8950
8960
8970
8980
8990
9000
9010
9020
9030
9040
9050
9060
9070
9080
9090
9100
9110
9120
9130
9140
9150
9160
9170
9180
9190
9200
9210
9220
9230
9240
9250
9260
9270
9280
9290
9300
9310
9320
9330
9340
9350
9360
9370
9380
9390
9400
9410
9420
9430
9440
9450
9460
9470
9480
9490
9500
9510
9520
9530
9540
9550
9560
9570
9580
9590
9600
9610
9620
9630
9640
9650
9660
9670
9680
9690
9701836
9710
9720
9730
9741836
9750
9760
9771836
9780
9793672
9801836
9811836
9820
9831836
9840
9850
9860
9870
9880
9890
9900
9910
9920
9930
9940
9950
9960
9970
9980
9990
10000
10010
10020
10030
10040
10050
10060
10070
10080
10090
10100
10110
10120
101318444
101418444
101518444
101618444
10170
10180
10190
10200
10210
102218444
102318444
102418444
10250
10260
10270
10280
10290
10300
10310
10329222
10339222
10349222
10350
10360
10370
10380
10399222
10409222
10419222
10420
10430
10440
10450
10460
10470
10480
10490
10500
10510
10520
10530
10540
10550
10560
10570
10580
10590
1060918
1061918
1062918
10630
10640
1065918
10660
10670
10680
10690
10700
10710
10720
10730
10740
10750
10760
10770
10780
10790
108030420
10819222
1082194
1083194
1084194
1085194
10860
10870
10880
10890
10900
10910
10920
10939028
10949028
10958528
10960
10970
109818444
10998528
11008528
11010
11020
11039222
11040
11050
11060
11070
11080
1109918
11100
11110
11120
11130
11140
11150
11160
11170
11180
11190
11200
11210
11222754
11230
11240
11250
11260
11270
11280
11290
11300
11310
11320
11330
11340
11350
11360
11370
11380
11390
11400
11410
11420
11430
11440
11450
11460
11470
11480
11490
11500
11510
11520
11530
11540
11550
11560
11570
11580
11590
11600
11610
11620
11630
11640
11650
11660
11670
11680
11690
11700
11710
11720
11730
11740
11750
11760
11770
11780
11790
11800
11810
11820
11830
11840
11850
11860
11870
11880
11890
11900
11910
11920
11930
11940
11950
11960
11970
11980
11990
12000
12010
12020
12030
12040
12050
12060
12070
12080
12090
12100
12110
12120
12130
12140
12150
12160
12170
12180
12190
12200
12210
12220
12230
12240
12250
12260
12270
12280
12290
12300
12310
12320
12330
12340
12350
12360
12370
12380
12390
12400
12410
12420
12430
12440
12450
12460
12470
12480
12490
12500
12510
12520
12530
12540
12550
12560
12570
12580
12590
12600
12610
12620
12630
12640
12650
12660
12670
12680
12690
12700
12710
12720
12730
12740
12750
12760
12770
12780
12790
12800
12810
12820
12830
12840
12850
12860
12870
12880
12890
12900
12910
12920
12930
12940
12950
12960
12970
12980
12990
13000
13010
13020
13030
13040
13050
13060
13070
13080
13090
13100
13110
13120
13130
13140
13150
13160
13170
13180
13190
13200
13210
13220
13230
13240
13250
13260
13270
13280
13290
13300
13310
13320
13330
13340
13350
13360
13370
13380
13390
13400
13410
13420
13430
13440
13450
13460
13470
13480
13490
13500
13510
13520
13530
13540
13550
13560
13570
13580
13590
13600
13610
13620
13630
13640
13650
13660
13670
13680
13690
13700
13710
13720
13730
13740
13750
13760
13770
13780
13790
13800
13810
13820
13830
13840
13850
13860
13870
13880
13890
13900
13910
13920
13930
13940
13950
13960
13970
13980
13990
14000
14010
14020
14030
14040
14050
14060
14070
14080
14090
14100
14110
14120
14130
14140
14150
14160
14170
14180
14190
14200
14210
14220
14230
14240
14250
14260
14270
14280
14290
14300
14310
14320
14330
14340
14350
14360
14370
14380
14390
14400
14410
14420
14430
14440
14450
14460
14470
14480
14490
14500
14510
14520
14530
14540
14550
14560
14570
14580
14590
14600
14610
14620
14630
14640
14650
14660
14670
14680
14690
14700
14710
14720
14730
14740
14750
14760
14770
14780
14790
14800
14810
14820
14830
14840
14850
14860
14870
14880
14890
14900
14910
14920
14930
14940
14950
14960
14970
14980
14990
15000
15010
15020
15030
15040
15050
15060
15070
15080
15090
15100
15110
15120
15130
15140
15150
15160
15170
15180
15190
15200
15210
15220
15230
15240
15250
15260
15270
15280
15290
15300
15310
15320
15330
15340
15350
15360
15370
15380
15390
15400
15410
15420
15430
15440
15450
15460
15470
15480
15490
15500
15510
15520
15530
15540
15550
15560
15570
15580
15590
15600
15610
15620
15630
15640
15650
15660
15670
15680
15690
15700
15710
15720
15730
15740
15750
15760
15770
15780
15790
15800
15810
15820
15830
15840
15850
15860
15870
15880
15890
15900
15910
15920
15930
15940
15950
15960
15970
15980
15990
16000
16010
16020
16030
16040
16050
16060
16070
16080
16090
16100
16110
16120
16130
16140
16150
16160
16170
16180
16190
16200
16210
16220
16230
16240
16250
16260
16270
16280
16290
16300
16310
16320
16330
16340
16350
16360
16370
16380
16390
16400
16410
16420
16430
16440
16450
16460
16470
16480
16490
16500
16510
16520
16530
16540
16550
16560
16570
16580
16590
16600
16610
16620
16630
16640
16650
16660
16670
16680
16690
16700
16710
16720
16730
16740
16750
16760
16770
16780
16790
16800
16810
16820
16830
16840
16850
16860
16870
16880
16890
16900
16910
16920
16930
16940
16950
16960
16970
16980
16990
17000
17010
17020
17030
17040
17050
17060
17070
17080
17090
17100
17110
17120
17130
17140
17150
17160
17170
17180
17190
17200
17210
17220
17230
17240
17250
17260
17270
17280
17290
17300
17310
17320
17330
17340
17350
17360
17370
17380
17390
17400
17410
17420
17430
17440
17450
17460
17470
17480
17490
17500
17510
17520
17530
17540
17550
17560
17570
17580
17590
17600
17610
17620
17630
17640
17650
17660
17670
17680
17690
17700
17710
17720
17730
17740
17750
17760
17770
17780
17790
17800
17810
17820
17830
17840
17850
17860
17870
17880
17890
17900
17910
17920
17930
17940
17950
17960
17970
17980
17990
18000
18010
18020
18030
18040
18050
18060
18070
18080
18090
18100
18110
18120
18130
18140
18150
18160
18170
18180
18190
18200
18210
18220
18230
18240
18250
18260
18270
18280
18290
18300
18310
18320
18330
18340
18350
18360
18370
18380
18390
18400
18410
18420
18430
18440
18450
18460
18470
18480
18490
18500
18510
18520
18530
18540
18550
18560
18570
18580
18590
18600
18610
18620
18630
18640
18650
18660
18670
18680
18690
18700
18710
18720
18730
18740
18750
18760
18770
18780
18790
18800
18810
18820
18830
18840
18850
18860
18870
18880
18890
18900
18910
18920
18930
18940
18950
18960
18970
18980
18990
19000
19010
19020
19030
19040
19050
19060
19070
19080
19090
19100
19110
19120
19130
19140
19150
19160
19170
19180
19190
19200
19210
19220
19230
19240
19250
19260
19270
19280
19290
19300
19310
19320
19330
19340
19350
19360
19370
19380
19390
19400
19410
19420
19430
19440
19450
19460
19470
19480
19490
19500
19510
19520
19530
19540
19550
19560
19570
19580
19590
19600
19610
19620
19630
19640
19650
19660
19670
19680
19690
19700
19710
19720
19730
19740
19750
19760
19770
19780
19790
19800
19810
19820
19830
19840
19850
19860
19870
19880
19890
19900
19910
19920
19930
19940
19950
19960
19970
19980
19990
20000
20010
20020
20030
20040
20050
20060
20070
20080
20090
20100
20110
20120
20130
20140
20150
20160
20170
20180
20190
20200
20210
20220
20230
20240
20250
20260
20270
20280
20290
20300
20310
20320
20330
20340
20350
20360
20370
20380
20390
20400
20410
20420
20430
20440
20450
20460
20470
20480
20490
20500
20510
20520
20530
20540
20550
20560
20570
20580
20590
20600
20610
20620
20630
20640
20650
20660
20670
20680
20690
20700
20710
20720
20730
20740
20750
20760
20770
20780
20790
20800
20810
20820
20830
20840
20850
20860
20870
20880
20890
20900
20910
20920
20930
20940
20950
20960
20970
20980
20990
21000
21010
21020
21030
21040
21050
21060
21070
21080
21090
21100
21110
21120
21130
21140
21150
21160
21170
21180
21190
21200
21210
21220
21230
21240
21250
21260
21270
21280
21290
21300
21310
21320
21330
21340
21350
21360
21370
21380
21390
21400
21410
21420
21430
21440
21450
21460
21470
21480
21490
21500
21510
21520
21530
21540
21550
21560
21570
21580
21590
21600
21610
21620
21630
21640
21650
21660
21670
21680
21690
21700
21710
21720
21730
21740
21750
21760
21770
21780
21790
21800
21810
21820
21830
21840
21850
21860
21870
21880
21890
21900
21910
21920
21930
21940
21950
21960
21970
21980
21990
22000
22010
22020
22030
22040
22050
22060
22070
22080
22090
22100
22110
22120
22130
22140
22150
22160
22170
22180
22190
22200
22210
22220
22230
22240
22250
22260
22270
22280
22290
22300
22310
22320
22330
22340
22350
22360
22370
22380
22390
22400
22410
22420
22430
22440
22450
22460
22470
22480
22490
22500
22510
22520
22530
22540
22550
22560
22570
22580
22590
22600
22610
22620
22630
22640
22650
22660
22670
22680
22690
22700
22710
22720
22730
22740
22750
22760
22770
22780
22790
22800
22810
22820
22830
22840
22850
22860
22870
22880
22890
22900
22910
22920
22930
22940
22950
22960
22970
22980
22990
23000
23010
23020
23030
23040
23050
23060
23070
23080
23090
23100
23110
23120
23130
23140
23150
23160
23170
23180
23190
23200
23210
23220
23230
23240
23250
23260
23270
23280
23290
23300
23310
23320
23330
23340
23350
23360
23370
23380
23390
23400
23410
23420
23430
23440
23450
23460
23470
23480
23490
23500
23510
23520
23530
23540
23550
23560
23570
23580
23590
23600
23610
23620
23630
23640
23650
23660
23670
23680
23690
23700
23710
23720
23730
23740
23750
23760
23770
23780
23790
23800
23810
23820
23830
23840
23850
23860
23870
23880
23890
23900
23910
23920
23930
23940
23950
23960
23970
23980
23990
24000
24010
24020
24030
24040
24050
24060
24070
24080
24090
24100
24110
24120
24130
24140
24150
24160
24170
24180
24190
24200
24210
24220
24230
24240
24250
24260
24270
24280
24290
24300
24310
24320
24330
24340
24350
24360
24370
24380
24390
24400
24410
24420
24430
24440
24450
24460
24470
24480
24490
24500
24510
24520
24530
24540
24550
24560
24570
24580
24590
24600
24610
24620
24630
24640
24650
24660
24670
24680
24690
24700
24710
24720
24730
24740
24750
24760
24770
24780
24790
24800
24810
24820
24830
24840
24850
24860
24870
24880
24890
24900
24910
24920
24930
24940
24950
24960
24970
24980
24990
25000
25010
25020
25030
25040
25050
25060
25070
25080
25090
25100
25110
25120
25130
25140
25150
25160
25170
25180
25190
25200
25210
25220
25230
25240
25250
25260
25270
25280
25290
25300
25310
25320
25330
25340
25350
25360
25370
25380
25390
25400
25410
25420
25430
25440
25450
25460
25470
25480
25490
25500
25510
25520
25530
25540
25550
25560
25570
25580
25590
25600
25610
25620
25630
25640
25650
25660
25670
25680
25690
25700
25710
25720
25730
25740
25750
25760
25770
25780
25790
25800
25810
25820
25830
25840
25850
25860
25870
25880
25890
25900
25910
25920
25930
25940
25950
25960
25970
25980
25990
26000
26010
26020
26030
26040
26050
26060
26070
26080
26090
26100
26110
26120
26130
26140
26150
26160
26170
26180
26190
26200
26210
26220
26230
26240
26250
26260
26270
26280
26290
26300
26310
26320
26330
26340
26350
26360
26370
26380
26390
26400
26410
26420
26430
26440
26450
26460
26470
26480
26490
26500
26510
26520
26530
26540
26550
26560
26570
26580
26590
26600
26610
26620
26630
26640
26650
26660
26670
26680
26690
26700
26710
26720
26730
26740
26750
26760
26770
26780
26790
26800
26810
26820
26830
26840
26850
26860
26870
26880
26890
26900
26910
26920
26930
26940
26950
26960
26970
26980
26990
27000
27010
27020
27030
27040
27050
27060
27070
27080
27090
27100
27110
27120
27130
27140
27150
27160
27170
27180
27190
27200
27210
27220
27230
27240
27250
27260
27270
27280
27290
27300
27310
27320
27330
27340
27350
27360
27370
27380
27390
27400
27410
27420
27430
27440
27450
27460
27470
27480
27490
27500
27510
27520
27530
27540
27550
27560
27570
27580
27590
27600
27610
27620
27630
27640
27650
27660
27670
27680
27690
27700
27710
27720
27730
27740
27750
27760
27770
27780
27790
27800
27810
27820
27830
27840
27850
27860
27870
27880
27890
27900
27910
27920
27930
27940
27950
27960
27970
27980
27990
28000
28010
28020
28030
28040
28050
28060
28070
28080
28090
28100
28110
28120
28130
28140
28150
28160
28170
28180
28190
28200
28210
28220
28230
28240
28250
28260
28270
28280
28290
28300
28310
28320
28330
28340
28350
28360
28370
28380
28390
28400
28410
28420
28430
28440
28450
28460
28470
28480
28490
28500
28510
28520
28530
28540
28550
28560
28570
28580
28590
28600
28610
28620
28630
28640
28650
28660
28670
28680
28690
28700
28710
28720
28730
28740
28750
28760
28770
28780
28790
28800
28810
28820
28830
28840
28850
28860
28870
28880
28890
28900
28910
28920
28930
28940
28950
28960
28970
28980
28990
29000
29010
29020
29030
29040
29050
29060
29070
29080
29090
29100
29110
29120
29130
29140
29150
29160
29170
29180
29190
29200
29210
29220
29230
29240
29250
29260
29270
29280
29290
29300
29310
29320
29330
29340
29350
29360
29370
29380
29390
29400
29410
29420
29430
29440
29450
29460
29470
29480
29490
29500
29510
29520
29530
29540
29550
29560
29570
29580
29590
29600
29610
29620
29630
29640
29650
29660
29670
29680
29690
29700
29710
29720
29730
29740
29750
29760
29770
29780
29790
29800
29810
29820
29830
29840
29850
29860
29870
29880
29890
29900
29910
29920
29930
29940
29950
29960
29970
29980
29990
30000
30010
30020
30030
30040
30050
30060
30070
30080
30090
30100
30110
30120
30130
30140
30150
30160
30170
30180
30190
30200
30210
30220
30230
30240
30250
30260
30270
30280
30290
30300
30310
30320
30330
30340
30350
30360
30370
30380
30390
30400
30410
30420
30430
30440
30450
30460
30470
30480
30490
30500
30510
30520
30530
30540
30550
30560
30570
30580
30590
30600
30610
30620
30630
30640
30650
30660
30670
30680
30690
30700
30710
30720
30730
30740
30750
30760
30770
30780
30790
30800
30810
30820
30830
30840
30850
30860
30870
30880
30890
30900
30910
30920
30930
30940
30950
30960
30970
30980
30990
31000
31010
31020
31030
31040
31050
31060
31070
31080
31090
31100
31110
31120
31130
31140
31150
31160
31170
31180
31190
31200
31210
31220
31230
31240
31250
31260
31270
31280
31290
31300
31310
31320
31330
31340
31350
31360
31370
31380
31390
31400
31410
31420
31430
31440
31450
31460
31470
31480
31490
31500
31510
31520
31530
31540
31550
31560
31570
31580
31590
31600
31610
31620
31630
31640
31650
31660
31670
31680
31690
31700
31710
31720
31730
31740
31750
31760
31770
31780
31790
31800
31810
31820
31830
31840
31850
31860
31870
31880
31890
31900
31910
31920
31930
31940
31950
31960
31970
31980
31990
32000
32010
32020
32030
32040
32050
32060
32070
32080
32090
32100
32110
32120
32130
32140
32150
32160
32170
32180
32190
32200
32210
32220
32230
32240
32250
32260
32270
32280
32290
32300
32310
32320
32330
32340
32350
32360
32370
32380
32390
32400
32410
32420
32430
32440
32450
32460
32470
32480
32490
32500
32510
32520
32530
32540
32550
32560
32570
32580
32590
32600
32610
32620
32630
32640
32650
32660
32670
32680
32690
32700
32710
32720
32730
32740
32750
32760
32770
32780
32790
32800
32810
32820
32830
32840
32850
32860
32870
32880
32890
32900
32910
32920
32930
32940
32950
32960
32970
32980
32990
33000
33010
33020
33030
33040
33050
33060
33070
33080
33090
33100
33110
33120
33130
33140
33150
33160
33170
33180
33190
33200
33210
33220
33230
33240
33250
33260
33270
33280
33290
33300
33310
33320
33330
33340
33350
33360
33370
33380
33390
33400
33410
33420
33430
33440
33450
33460
33470
33480
33490
33500
33510
33520
33530
33540
33550
33560
33570
33580
33590
33600
33610
33620
33630
33640
33650
33660
33670
33680
33690
33700
33710
33720
33730
33740
33750
33760
33770
33780
33790
33800
33810
33820
33830
33840
33850
33860
33870
33880
33890
33900
33910
33920
33930
33940
33950
33960
33970
33980
33990
34000
34010
34020
34030
34040
34050
34060
34070
34080
34090
34100
34110
34120
34130
34140
34150
34160
34170
34180
34190
34200
34210
34220
34230
34240
34250
34260
34270
34280
34290
34300
34310
34320
34330
34340
34350
34360
34370
34380
34390
34400
34410
34420
34430
34440
34450
34460
34470
34480
34490
34500
34510
34520
34530
34540
34550
34560
34570
34580
34590
34600
34610
34620
34630
34640
34650
34660
34670
34680
34690
34700
34710
34720
34730
34740
34750
34760
34770
34780
34790
34800
34810
34820
34830
34840
34850
34860
34870
34880
34890
34900
34910
34920
34930
34940
34950
34960
34970
34980
34990
35000
35010
35020
35030
35040
35050
35060
35070
35080
35090
35100
35110
35120
35130
35140
35150
35160
35170
35180
35190
35200
35210
35220
35230
35240
35250
35260
35270
35280
35290
35300
35310
35320
35330
35340
35350
35360
35370
35380
35390
35400
35410
35420
35430
35440
35450
35460
35470
35480
35490
35500
35510
35520
35530
35540
35550
35560
35570
35580
35590
35600
35610
35620
35630
35640
35650
35660
35670
35680
35690
35700
35710
35720
35730
35740
35750
35760
35770
35780
35790
35800
35810
35820
35830
35840
35850
35860
35870
35880
35890
35900
35910
35920
35930
35940
35950
35960
35970
35980
35990
36000
36010
36020
36030
36040
36050
36060
36070
36080
36090
36100
36110
36120
36130
36140
36150
36160
36170
36180
36190
36200
36210
36220
36230
36240
36250
36260
36270
36280
36290
36300
36310
36320
36330
36340
36350
36360
36370
36380
36390
36400
36410
36420
module trial.terminal; version(Posix) { enum SIGWINCH = 28; __gshared bool windowSizeChanged = false; __gshared bool interrupted = false; __gshared bool hangedUp = false; version(with_eventloop) struct SignalFired {} extern(C) void sizeSignalHandler(int sigNumber) nothrow { windowSizeChanged = true; version(with_eventloop) { import arsd.eventloop; try send(SignalFired()); catch(Exception) {} } } extern(C) void interruptSignalHandler(int sigNumber) nothrow { interrupted = true; version(with_eventloop) { import arsd.eventloop; try send(SignalFired()); catch(Exception) {} } } extern(C) void hangupSignalHandler(int sigNumber) nothrow { hangedUp = true; version(with_eventloop) { import arsd.eventloop; try send(SignalFired()); catch(Exception) {} } } } version(Windows) { import core.sys.windows.windows; import std.string : toStringz; private { enum RED_BIT = 4; enum GREEN_BIT = 2; enum BLUE_BIT = 1; } } version(Posix) { import core.sys.posix.termios; import core.sys.posix.unistd; import unix = core.sys.posix.unistd; import core.sys.posix.sys.types; import core.sys.posix.sys.time; import core.stdc.stdio; private { enum RED_BIT = 1; enum GREEN_BIT = 2; enum BLUE_BIT = 4; } version(linux) { extern(C) int ioctl(int, int, ...); enum int TIOCGWINSZ = 0x5413; } else version(OSX) { import core.stdc.config; extern(C) int ioctl(int, c_ulong, ...); enum TIOCGWINSZ = 1074295912; } else static assert(0, "confirm the value of tiocgwinsz"); struct winsize { ushort ws_row; ushort ws_col; ushort ws_xpixel; ushort ws_ypixel; } enum string builtinTermcap = ` # Generic VT entry. vg|vt-generic|Generic VT entries:\ :bs:mi:ms:pt:xn:xo:it#8:\ :RA=\E[?7l:SA=\E?7h:\ :bl=^G:cr=^M:ta=^I:\ :cm=\E[%i%d;%dH:\ :le=^H:up=\E[A:do=\E[B:nd=\E[C:\ :LE=\E[%dD:RI=\E[%dC:UP=\E[%dA:DO=\E[%dB:\ :ho=\E[H:cl=\E[H\E[2J:ce=\E[K:cb=\E[1K:cd=\E[J:sf=\ED:sr=\EM:\ :ct=\E[3g:st=\EH:\ :cs=\E[%i%d;%dr:sc=\E7:rc=\E8:\ :ei=\E[4l:ic=\E[@:IC=\E[%d@:al=\E[L:AL=\E[%dL:\ :dc=\E[P:DC=\E[%dP:dl=\E[M:DL=\E[%dM:\ :so=\E[7m:se=\E[m:us=\E[4m:ue=\E[m:\ :mb=\E[5m:mh=\E[2m:md=\E[1m:mr=\E[7m:me=\E[m:\ :sc=\E7:rc=\E8:kb=\177:\ :ku=\E[A:kd=\E[B:kr=\E[C:kl=\E[D: # Slackware 3.1 linux termcap entry (Sat Apr 27 23:03:58 CDT 1996): lx|linux|console|con80x25|LINUX System Console:\ :do=^J:co#80:li#25:cl=\E[H\E[J:sf=\ED:sb=\EM:\ :le=^H:bs:am:cm=\E[%i%d;%dH:nd=\E[C:up=\E[A:\ :ce=\E[K:cd=\E[J:so=\E[7m:se=\E[27m:us=\E[36m:ue=\E[m:\ :md=\E[1m:mr=\E[7m:mb=\E[5m:me=\E[m:is=\E[1;25r\E[25;1H:\ :ll=\E[1;25r\E[25;1H:al=\E[L:dc=\E[P:dl=\E[M:\ :it#8:ku=\E[A:kd=\E[B:kr=\E[C:kl=\E[D:kb=^H:ti=\E[r\E[H:\ :ho=\E[H:kP=\E[5~:kN=\E[6~:kH=\E[4~:kh=\E[1~:kD=\E[3~:kI=\E[2~:\ :k1=\E[[A:k2=\E[[B:k3=\E[[C:k4=\E[[D:k5=\E[[E:k6=\E[17~:\ :F1=\E[23~:F2=\E[24~:\ :k7=\E[18~:k8=\E[19~:k9=\E[20~:k0=\E[21~:K1=\E[1~:K2=\E[5~:\ :K4=\E[4~:K5=\E[6~:\ :pt:sr=\EM:vt#3:xn:km:bl=^G:vi=\E[?25l:ve=\E[?25h:vs=\E[?25h:\ :sc=\E7:rc=\E8:cs=\E[%i%d;%dr:\ :r1=\Ec:r2=\Ec:r3=\Ec: # Some other, commonly used linux console entries. lx|con80x28:co#80:li#28:tc=linux: lx|con80x43:co#80:li#43:tc=linux: lx|con80x50:co#80:li#50:tc=linux: lx|con100x37:co#100:li#37:tc=linux: lx|con100x40:co#100:li#40:tc=linux: lx|con132x43:co#132:li#43:tc=linux: # vt102 - vt100 + insert line etc. VT102 does not have insert character. v2|vt102|DEC vt102 compatible:\ :co#80:li#24:\ :ic@:IC@:\ :is=\E[m\E[?1l\E>:\ :rs=\E[m\E[?1l\E>:\ :eA=\E)0:as=^N:ae=^O:ac=aaffggjjkkllmmnnooqqssttuuvvwwxx:\ :ks=:ke=:\ :k1=\EOP:k2=\EOQ:k3=\EOR:k4=\EOS:\ :tc=vt-generic: # vt100 - really vt102 without insert line, insert char etc. vt|vt100|DEC vt100 compatible:\ :im@:mi@:al@:dl@:ic@:dc@:AL@:DL@:IC@:DC@:\ :tc=vt102: # Entry for an xterm. Insert mode has been disabled. vs|xterm|xterm-color|xterm-256color|vs100|xterm terminal emulator (X Window System):\ :am:bs:mi@:km:co#80:li#55:\ :im@:ei@:\ :cl=\E[H\E[J:\ :ct=\E[3k:ue=\E[m:\ :is=\E[m\E[?1l\E>:\ :rs=\E[m\E[?1l\E>:\ :vi=\E[?25l:ve=\E[?25h:\ :eA=\E)0:as=^N:ae=^O:ac=aaffggjjkkllmmnnooqqssttuuvvwwxx:\ :kI=\E[2~:kD=\E[3~:kP=\E[5~:kN=\E[6~:\ :k1=\EOP:k2=\EOQ:k3=\EOR:k4=\EOS:k5=\E[15~:\ :k6=\E[17~:k7=\E[18~:k8=\E[19~:k9=\E[20~:k0=\E[21~:\ :F1=\E[23~:F2=\E[24~:\ :kh=\E[H:kH=\E[F:\ :ks=:ke=:\ :te=\E[2J\E[?47l\E8:ti=\E7\E[?47h:\ :tc=vt-generic: #rxvt, added by me rxvt|rxvt-unicode:\ :am:bs:mi@:km:co#80:li#55:\ :im@:ei@:\ :ct=\E[3k:ue=\E[m:\ :is=\E[m\E[?1l\E>:\ :rs=\E[m\E[?1l\E>:\ :vi=\E[?25l:\ :ve=\E[?25h:\ :eA=\E)0:as=^N:ae=^O:ac=aaffggjjkkllmmnnooqqssttuuvvwwxx:\ :kI=\E[2~:kD=\E[3~:kP=\E[5~:kN=\E[6~:\ :k1=\E[11~:k2=\E[12~:k3=\E[13~:k4=\E[14~:k5=\E[15~:\ :k6=\E[17~:k7=\E[18~:k8=\E[19~:k9=\E[20~:k0=\E[21~:\ :F1=\E[23~:F2=\E[24~:\ :kh=\E[7~:kH=\E[8~:\ :ks=:ke=:\ :te=\E[2J\E[?47l\E8:ti=\E7\E[?47h:\ :tc=vt-generic: # Some other entries for the same xterm. v2|xterms|vs100s|xterm small window:\ :co#80:li#24:tc=xterm: vb|xterm-bold|xterm with bold instead of underline:\ :us=\E[1m:tc=xterm: vi|xterm-ins|xterm with insert mode:\ :mi:im=\E[4h:ei=\E[4l:tc=xterm: Eterm|Eterm Terminal Emulator (X11 Window System):\ :am:bw:eo:km:mi:ms:xn:xo:\ :co#80:it#8:li#24:lm#0:pa#64:Co#8:AF=\E[3%dm:AB=\E[4%dm:op=\E[39m\E[49m:\ :AL=\E[%dL:DC=\E[%dP:DL=\E[%dM:DO=\E[%dB:IC=\E[%d@:\ :K1=\E[7~:K2=\EOu:K3=\E[5~:K4=\E[8~:K5=\E[6~:LE=\E[%dD:\ :RI=\E[%dC:UP=\E[%dA:ae=^O:al=\E[L:as=^N:bl=^G:cd=\E[J:\ :ce=\E[K:cl=\E[H\E[2J:cm=\E[%i%d;%dH:cr=^M:\ :cs=\E[%i%d;%dr:ct=\E[3g:dc=\E[P:dl=\E[M:do=\E[B:\ :ec=\E[%dX:ei=\E[4l:ho=\E[H:i1=\E[?47l\E>\E[?1l:ic=\E[@:\ :im=\E[4h:is=\E[r\E[m\E[2J\E[H\E[?7h\E[?1;3;4;6l\E[4l:\ :k1=\E[11~:k2=\E[12~:k3=\E[13~:k4=\E[14~:k5=\E[15~:\ :k6=\E[17~:k7=\E[18~:k8=\E[19~:k9=\E[20~:kD=\E[3~:\ :kI=\E[2~:kN=\E[6~:kP=\E[5~:kb=^H:kd=\E[B:ke=:kh=\E[7~:\ :kl=\E[D:kr=\E[C:ks=:ku=\E[A:le=^H:mb=\E[5m:md=\E[1m:\ :me=\E[m\017:mr=\E[7m:nd=\E[C:rc=\E8:\ :sc=\E7:se=\E[27m:sf=^J:so=\E[7m:sr=\EM:st=\EH:ta=^I:\ :te=\E[2J\E[?47l\E8:ti=\E7\E[?47h:ue=\E[24m:up=\E[A:\ :us=\E[4m:vb=\E[?5h\E[?5l:ve=\E[?25h:vi=\E[?25l:\ :ac=aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~: # DOS terminal emulator such as Telix or TeleMate. # This probably also works for the SCO console, though it's incomplete. an|ansi|ansi-bbs|ANSI terminals (emulators):\ :co#80:li#24:am:\ :is=:rs=\Ec:kb=^H:\ :as=\E[m:ae=:eA=:\ :ac=0\333+\257,\256.\031-\030a\261f\370g\361j\331k\277l\332m\300n\305q\304t\264u\303v\301w\302x\263~\025:\ :kD=\177:kH=\E[Y:kN=\E[U:kP=\E[V:kh=\E[H:\ :k1=\EOP:k2=\EOQ:k3=\EOR:k4=\EOS:k5=\EOT:\ :k6=\EOU:k7=\EOV:k8=\EOW:k9=\EOX:k0=\EOY:\ :tc=vt-generic: `; } enum Bright = 0x08; enum Color : ushort { black = 0, red = RED_BIT, green = GREEN_BIT, yellow = red | green, blue = BLUE_BIT, magenta = red | blue, cyan = blue | green, white = red | green | blue, DEFAULT = 256, } enum ConsoleInputFlags { raw = 0, echo = 1, mouse = 2, paste = 4, size = 8, releasedKeys = 64, allInputEvents = 8|4|2, allInputEventsWithRelease = allInputEvents|releasedKeys, } enum ConsoleOutputType { linear = 0, cellular = 1, minimalProcessing = 255, } enum ForceOption { automatic = 0, neverSend = -1, alwaysSend = 1, } struct Terminal { @disable this(); @disable this(this); private ConsoleOutputType type; version(Posix) { private int fdOut; private int fdIn; private int[] delegate() getSizeOverride; void delegate(in void[]) _writeDelegate; } version(Posix) { bool terminalInFamily(string[] terms...) { import std.process; import std.string; auto term = environment.get("TERM"); foreach(t; terms) if(indexOf(term, t) != -1) return true; return false; } bool isMacTerminal() { import std.process; import std.string; auto term = environment.get("TERM"); return term == "xterm-256color"; } static string[string] termcapDatabase; static void readTermcapFile(bool useBuiltinTermcap = false) { import std.file; import std.stdio; import std.string; if(!exists("/etc/termcap")) useBuiltinTermcap = true; string current; void commitCurrentEntry() { if(current is null) return; string names = current; auto idx = indexOf(names, ":"); if(idx != -1) names = names[0 .. idx]; foreach(name; split(names, "|")) termcapDatabase[name] = current; current = null; } void handleTermcapLine(in char[] line) { if(line.length == 0) { commitCurrentEntry(); return; } if(line[0] == '#') return; size_t termination = line.length; if(line[$-1] == '\\') termination--; current ~= strip(line[0 .. termination]); if(line[$-1] != '\\') commitCurrentEntry(); } if(useBuiltinTermcap) { foreach(line; splitLines(builtinTermcap)) { handleTermcapLine(line); } } else { foreach(line; File("/etc/termcap").byLine()) { handleTermcapLine(line); } } } static string getTermcapDatabase(string terminal) { import std.string; if(termcapDatabase is null) readTermcapFile(); auto data = terminal in termcapDatabase; if(data is null) return null; auto tc = *data; auto more = indexOf(tc, ":tc="); if(more != -1) { auto tcKey = tc[more + ":tc=".length .. $]; auto end = indexOf(tcKey, ":"); if(end != -1) tcKey = tcKey[0 .. end]; tc = getTermcapDatabase(tcKey) ~ tc; } return tc; } string[string] termcap; void readTermcap() { import std.process; import std.string; import std.array; string termcapData = environment.get("TERMCAP"); if(termcapData.length == 0) { termcapData = getTermcapDatabase(environment.get("TERM")); } auto e = replace(termcapData, "\\\n", "\n"); termcap = null; foreach(part; split(e, ":")) { auto things = split(part, "="); if(things.length) termcap[things[0]] = things.length > 1 ? things[1] : null; } } string findSequenceInTermcap(in char[] sequenceIn) { char[10] sequenceBuffer; char[] sequence; if(sequenceIn.length > 0 && sequenceIn[0] == '\033') { if(!(sequenceIn.length < sequenceBuffer.length - 1)) return null; sequenceBuffer[1 .. sequenceIn.length + 1] = sequenceIn[]; sequenceBuffer[0] = '\\'; sequenceBuffer[1] = 'E'; sequence = sequenceBuffer[0 .. sequenceIn.length + 1]; } else { sequence = sequenceBuffer[1 .. sequenceIn.length + 1]; } import std.array; foreach(k, v; termcap) if(v == sequence) return k; return null; } string getTermcap(string key) { auto k = key in termcap; if(k !is null) return *k; return null; } bool doTermcap(T...)(string key, T t) { import std.conv; auto fs = getTermcap(key); if(fs is null) return false; int swapNextTwo = 0; R getArg(R)(int idx) { if(swapNextTwo == 2) { idx ++; swapNextTwo--; } else if(swapNextTwo == 1) { idx --; swapNextTwo--; } foreach(i, arg; t) { if(i == idx) return to!R(arg); } assert(0, to!string(idx) ~ " is out of bounds working " ~ fs); } char[256] buffer; int bufferPos = 0; void addChar(char c) { import std.exception; enforce(bufferPos < buffer.length); buffer[bufferPos++] = c; } void addString(in char[] c) { import std.exception; enforce(bufferPos + c.length < buffer.length); buffer[bufferPos .. bufferPos + c.length] = c[]; bufferPos += c.length; } void addInt(int c, int minSize) { import std.string; auto str = format("%0"~(minSize ? to!string(minSize) : "")~"d", c); addString(str); } bool inPercent; int argPosition = 0; int incrementParams = 0; bool skipNext; bool nextIsChar; bool inBackslash; foreach(char c; fs) { if(inBackslash) { if(c == 'E') addChar('\033'); else addChar(c); inBackslash = false; } else if(nextIsChar) { if(skipNext) skipNext = false; else addChar(cast(char) (c + getArg!int(argPosition) + (incrementParams ? 1 : 0))); if(incrementParams) incrementParams--; argPosition++; inPercent = false; } else if(inPercent) { switch(c) { case '%': addChar('%'); inPercent = false; break; case '2': case '3': case 'd': if(skipNext) skipNext = false; else addInt(getArg!int(argPosition) + (incrementParams ? 1 : 0), c == 'd' ? 0 : (c - '0') ); if(incrementParams) incrementParams--; argPosition++; inPercent = false; break; case '.': if(skipNext) skipNext = false; else addChar(cast(char) (getArg!int(argPosition) + (incrementParams ? 1 : 0))); if(incrementParams) incrementParams--; argPosition++; break; case '+': nextIsChar = true; inPercent = false; break; case 'i': incrementParams = 2; inPercent = false; break; case 's': skipNext = true; inPercent = false; break; case 'b': argPosition--; inPercent = false; break; case 'r': swapNextTwo = 2; inPercent = false; break; default: assert(0, "not supported " ~ c); } } else { if(c == '%') inPercent = true; else if(c == '\\') inBackslash = true; else addChar(c); } } writeStringRaw(buffer[0 .. bufferPos]); return true; } } version(Posix) this(ConsoleOutputType type, int fdIn = 0, int fdOut = 1, int[] delegate() getSizeOverride = null) { this.fdIn = fdIn; this.fdOut = fdOut; this.getSizeOverride = getSizeOverride; this.type = type; readTermcap(); if(type == ConsoleOutputType.minimalProcessing) { _suppressDestruction = true; return; } if(type == ConsoleOutputType.cellular) { doTermcap("ti"); clear(); moveTo(0, 0, ForceOption.alwaysSend); } if(terminalInFamily("xterm", "rxvt", "screen")) { writeStringRaw("\033[22;0t"); } } version(Windows) { HANDLE hConsole; CONSOLE_SCREEN_BUFFER_INFO originalSbi; } version(Windows) this(ConsoleOutputType type) { if(type == ConsoleOutputType.cellular) { hConsole = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE, 0, null, CONSOLE_TEXTMODE_BUFFER, null); if(hConsole == INVALID_HANDLE_VALUE) { import std.conv; throw new Exception(to!string(GetLastError())); } SetConsoleActiveScreenBuffer(hConsole); COORD size; clear(); } else { hConsole = GetStdHandle(STD_OUTPUT_HANDLE); } GetConsoleScreenBufferInfo(hConsole, &originalSbi); } bool _suppressDestruction; version(Posix) ~this() { if(_suppressDestruction) { flush(); return; } if(type == ConsoleOutputType.cellular) { doTermcap("te"); } if(terminalInFamily("xterm", "rxvt", "screen")) { writeStringRaw("\033[23;0t"); } showCursor(); reset(); flush(); if(lineGetter !is null) lineGetter.dispose(); } version(Windows) ~this() { flush(); reset(); showCursor(); if(lineGetter !is null) lineGetter.dispose(); auto stdo = GetStdHandle(STD_OUTPUT_HANDLE); SetConsoleActiveScreenBuffer(stdo); if(hConsole !is stdo) CloseHandle(hConsole); } LineGetter lineGetter; int _currentForeground = Color.DEFAULT; int _currentBackground = Color.DEFAULT; RGB _currentForegroundRGB; RGB _currentBackgroundRGB; bool reverseVideo = false; bool setTrueColor(RGB foreground, RGB background, ForceOption force = ForceOption.automatic) { if(force == ForceOption.neverSend) { _currentForeground = -1; _currentBackground = -1; _currentForegroundRGB = foreground; _currentBackgroundRGB = background; return true; } if(force == ForceOption.automatic && _currentForeground == -1 && _currentBackground == -1 && (_currentForegroundRGB == foreground && _currentBackgroundRGB == background)) return true; _currentForeground = -1; _currentBackground = -1; _currentForegroundRGB = foreground; _currentBackgroundRGB = background; version(Windows) { flush(); ushort setTob = cast(ushort) approximate16Color(background); ushort setTof = cast(ushort) approximate16Color(foreground); SetConsoleTextAttribute( hConsole, cast(ushort)((setTob << 4) | setTof)); return false; } else { import std.process; import std.string; if(environment.get("TERM") == "rxvt" || environment.get("TERM") == "linux") { auto setTof = approximate16Color(foreground); auto setTob = approximate16Color(background); writeStringRaw(format("\033[%dm\033[3%dm\033[4%dm", (setTof & Bright) ? 1 : 0, cast(int) (setTof & ~Bright), cast(int) (setTob & ~Bright) )); return false; } writeStringRaw(format("\033[38;5;%dm\033[48;5;%dm", colorToXTermPaletteIndex(foreground), colorToXTermPaletteIndex(background) )); return true; } } void color(int foreground, int background, ForceOption force = ForceOption.automatic, bool reverseVideo = false) { if(force != ForceOption.neverSend) { version(Windows) { ushort setTof = cast(ushort) foreground; ushort setTob = cast(ushort) background; if(background == Color.DEFAULT) setTob = Color.black; if(foreground == Color.DEFAULT) setTof = Color.white; if(force == ForceOption.alwaysSend || reverseVideo != this.reverseVideo || foreground != _currentForeground || background != _currentBackground) { flush(); if(reverseVideo) { if(background == Color.DEFAULT) setTof = Color.black; else setTof = cast(ushort) background | (foreground & Bright); if(background == Color.DEFAULT) setTob = Color.white; else setTob = cast(ushort) (foreground & ~Bright); } SetConsoleTextAttribute( hConsole, cast(ushort)((setTob << 4) | setTof)); } } else { import std.process; ushort setTof = cast(ushort) foreground & ~Bright; ushort setTob = cast(ushort) background & ~Bright; if(foreground & Color.DEFAULT) setTof = 9; if(background == Color.DEFAULT) setTob = 9; import std.string; if(force == ForceOption.alwaysSend || reverseVideo != this.reverseVideo || foreground != _currentForeground || background != _currentBackground) { writeStringRaw(format("\033[%dm\033[3%dm\033[4%dm\033[%dm", (foreground != Color.DEFAULT && (foreground & Bright)) ? 1 : 0, cast(int) setTof, cast(int) setTob, reverseVideo ? 7 : 27 )); } } } _currentForeground = foreground; _currentBackground = background; this.reverseVideo = reverseVideo; } private bool _underlined = false; void underline(bool set, ForceOption force = ForceOption.automatic) { if(set == _underlined && force != ForceOption.alwaysSend) return; version(Posix) { if(set) writeStringRaw("\033[4m"); else writeStringRaw("\033[24m"); } _underlined = set; } void reset() { version(Windows) SetConsoleTextAttribute( hConsole, originalSbi.wAttributes); else writeStringRaw("\033[0m"); _underlined = false; _currentForeground = Color.DEFAULT; _currentBackground = Color.DEFAULT; reverseVideo = false; } @property int cursorX() { return _cursorX; } @property int cursorY() { return _cursorY; } private int _cursorX; private int _cursorY; void moveTo(int x, int y, ForceOption force = ForceOption.automatic) { if(force != ForceOption.neverSend && (force == ForceOption.alwaysSend || x != _cursorX || y != _cursorY)) { executeAutoHideCursor(); version(Posix) { doTermcap("cm", y, x); } else version(Windows) { flush(); COORD coord = {cast(short) x, cast(short) y}; SetConsoleCursorPosition(hConsole, coord); } else static assert(0); } _cursorX = x; _cursorY = y; } void showCursor() { version(Posix) doTermcap("ve"); else { CONSOLE_CURSOR_INFO info; GetConsoleCursorInfo(hConsole, &info); info.bVisible = true; SetConsoleCursorInfo(hConsole, &info); } } void hideCursor() { version(Posix) { doTermcap("vi"); } else { CONSOLE_CURSOR_INFO info; GetConsoleCursorInfo(hConsole, &info); info.bVisible = false; SetConsoleCursorInfo(hConsole, &info); } } private bool autoHidingCursor; private bool autoHiddenCursor; void autoHideCursor() { autoHidingCursor = true; } private void executeAutoHideCursor() { if(autoHidingCursor) { version(Windows) hideCursor(); else version(Posix) { writeBuffer = "\033[?25l" ~ writeBuffer; } autoHiddenCursor = true; autoHidingCursor = false; } } void autoShowCursor() { if(autoHiddenCursor) showCursor(); autoHidingCursor = false; autoHiddenCursor = false; } void setTitle(string t) { version(Windows) { SetConsoleTitleA(toStringz(t)); } else { import std.string; if(terminalInFamily("xterm", "rxvt", "screen")) writeStringRaw(format("\033]0;%s\007", t)); } } void flush() { if(writeBuffer.length == 0) return; version(Posix) { if(_writeDelegate !is null) { _writeDelegate(writeBuffer); } else { ssize_t written; while(writeBuffer.length) { written = unix.write(this.fdOut, writeBuffer.ptr, writeBuffer.length); if(written < 0) throw new Exception("write failed for some reason"); writeBuffer = writeBuffer[written .. $]; } } } else version(Windows) { import std.conv; wstring writeBufferw = to!wstring(writeBuffer); while(writeBufferw.length) { DWORD written; WriteConsoleW(hConsole, writeBufferw.ptr, cast(DWORD)writeBufferw.length, &written, null); writeBufferw = writeBufferw[written .. $]; } writeBuffer = null; } } int[] getSize() { version(Windows) { CONSOLE_SCREEN_BUFFER_INFO info; GetConsoleScreenBufferInfo( hConsole, &info ); int cols, rows; cols = (info.srWindow.Right - info.srWindow.Left + 1); rows = (info.srWindow.Bottom - info.srWindow.Top + 1); return [cols, rows]; } else { if(getSizeOverride is null) { winsize w; ioctl(0, TIOCGWINSZ, &w); return [w.ws_col, w.ws_row]; } else return getSizeOverride(); } } void updateSize() { auto size = getSize(); _width = size[0]; _height = size[1]; } private int _width; private int _height; @property int width() { if(_width == 0 || _height == 0) updateSize(); return _width; } @property int height() { if(_width == 0 || _height == 0) updateSize(); return _height; } void writef(T...)(string f, T t) { import std.string; writePrintableString(format(f, t)); } void writefln(T...)(string f, T t) { writef(f ~ "\n", t); } void write(T...)(T t) { import std.conv; string data; foreach(arg; t) { data ~= to!string(arg); } writePrintableString(data); } void writeln(T...)(T t) { write(t, "\n"); } void writePrintableString(in char[] s, ForceOption force = ForceOption.automatic) { foreach(ch; s) { switch(ch) { case '\n': _cursorX = 0; _cursorY++; break; case '\r': _cursorX = 0; break; case '\t': _cursorX ++; _cursorX += _cursorX % 8; break; default: if(ch <= 127) _cursorX++; } if(_wrapAround && _cursorX > width) { _cursorX = 0; _cursorY++; } if(_cursorY == height) _cursorY--; } writeStringRaw(s); } bool _wrapAround = true; deprecated alias writePrintableString writeString; private string writeBuffer; void writeStringRaw(in char[] s) { version(Posix) { writeBuffer ~= s; } else version(Windows) { writeBuffer ~= s; } else static assert(0); } void clear() { version(Posix) { doTermcap("cl"); } else version(Windows) { flush(); DWORD c; CONSOLE_SCREEN_BUFFER_INFO csbi; DWORD conSize; GetConsoleScreenBufferInfo(hConsole, &csbi); conSize = csbi.dwSize.X * csbi.dwSize.Y; COORD coordScreen; FillConsoleOutputCharacterA(hConsole, ' ', conSize, coordScreen, &c); FillConsoleOutputAttribute(hConsole, csbi.wAttributes, conSize, coordScreen, &c); moveTo(0, 0, ForceOption.alwaysSend); } _cursorX = 0; _cursorY = 0; } string getline(string prompt = null) { if(lineGetter is null) lineGetter = new LineGetter(&this); lineGetter.terminal = &this; if(prompt !is null) lineGetter.prompt = prompt; auto input = RealTimeConsoleInput(&this, ConsoleInputFlags.raw); auto line = lineGetter.getline(&input); writePrintableString("\n"); return line; } } struct RealTimeConsoleInput { @disable this(); @disable this(this); version(Posix) { private int fdOut; private int fdIn; private sigaction_t oldSigWinch; private sigaction_t oldSigIntr; private sigaction_t oldHupIntr; private termios old; ubyte[128] hack; } version(Windows) { private DWORD oldInput; private DWORD oldOutput; HANDLE inputHandle; } private ConsoleInputFlags flags; private Terminal* terminal; private void delegate()[] destructor; public this(Terminal* terminal, ConsoleInputFlags flags) { this.flags = flags; this.terminal = terminal; version(Windows) { inputHandle = GetStdHandle(STD_INPUT_HANDLE); GetConsoleMode(inputHandle, &oldInput); DWORD mode = 0; mode |= ENABLE_PROCESSED_INPUT ; mode |= ENABLE_WINDOW_INPUT ; if(flags & ConsoleInputFlags.echo) mode |= ENABLE_ECHO_INPUT; if(flags & ConsoleInputFlags.mouse) mode |= ENABLE_MOUSE_INPUT; SetConsoleMode(inputHandle, mode); destructor ~= { SetConsoleMode(inputHandle, oldInput); }; GetConsoleMode(terminal.hConsole, &oldOutput); mode = 0; mode |= ENABLE_PROCESSED_OUTPUT; mode |= ENABLE_WRAP_AT_EOL_OUTPUT; SetConsoleMode(terminal.hConsole, mode); destructor ~= { SetConsoleMode(terminal.hConsole, oldOutput); }; } version(Posix) { this.fdIn = terminal.fdIn; this.fdOut = terminal.fdOut; if(fdIn != -1) { tcgetattr(fdIn, &old); auto n = old; auto f = ICANON; if(!(flags & ConsoleInputFlags.echo)) f |= ECHO; n.c_lflag &= ~f; tcsetattr(fdIn, TCSANOW, &n); } if(flags & ConsoleInputFlags.size) { import core.sys.posix.signal; sigaction_t n; n.sa_handler = &sizeSignalHandler; n.sa_mask = cast(sigset_t) 0; n.sa_flags = 0; sigaction(SIGWINCH, &n, &oldSigWinch); } { import core.sys.posix.signal; sigaction_t n; n.sa_handler = &interruptSignalHandler; n.sa_mask = cast(sigset_t) 0; n.sa_flags = 0; sigaction(SIGINT, &n, &oldSigIntr); } { import core.sys.posix.signal; sigaction_t n; n.sa_handler = &hangupSignalHandler; n.sa_mask = cast(sigset_t) 0; n.sa_flags = 0; sigaction(SIGHUP, &n, &oldHupIntr); } if(flags & ConsoleInputFlags.mouse) { terminal.writeStringRaw("\033[?1000h"); destructor ~= { terminal.writeStringRaw("\033[?1000l"); }; import std.process : environment; if(terminal.terminalInFamily("xterm") && environment.get("MOUSE_HACK") != "1002") { terminal.writeStringRaw("\033[?1003h"); destructor ~= { terminal.writeStringRaw("\033[?1003l"); }; } else if(terminal.terminalInFamily("rxvt", "screen") || environment.get("MOUSE_HACK") == "1002") { terminal.writeStringRaw("\033[?1002h"); destructor ~= { terminal.writeStringRaw("\033[?1002l"); }; } } if(flags & ConsoleInputFlags.paste) { if(terminal.terminalInFamily("xterm", "rxvt", "screen")) { terminal.writeStringRaw("\033[?2004h"); destructor ~= { terminal.writeStringRaw("\033[?2004l"); }; } } if(terminal.terminalInFamily("xterm", "screen", "linux") && !terminal.isMacTerminal()) { terminal.writeStringRaw("\033%G"); } terminal.flush(); } version(with_eventloop) { import arsd.eventloop; version(Windows) auto listenTo = inputHandle; else version(Posix) auto listenTo = this.fdIn; else static assert(0, "idk about this OS"); version(Posix) addListener(&signalFired); if(listenTo != -1) { addFileEventListeners(listenTo, &eventListener, null, null); destructor ~= { removeFileEventListeners(listenTo); }; } addOnIdle(&terminal.flush); destructor ~= { removeOnIdle(&terminal.flush); }; } } version(with_eventloop) { version(Posix) void signalFired(SignalFired) { if(interrupted) { interrupted = false; send(InputEvent(UserInterruptionEvent(), terminal)); } if(windowSizeChanged) send(checkWindowSizeChanged()); if(hangedUp) { hangedUp = false; send(InputEvent(HangupEvent(), terminal)); } } import arsd.eventloop; void eventListener(OsFileHandle fd) { auto queue = readNextEvents(); foreach(event; queue) send(event); } } ~this() { version(Posix) if(fdIn != -1) tcsetattr(fdIn, TCSANOW, &old); version(Posix) { if(flags & ConsoleInputFlags.size) { sigaction(SIGWINCH, &oldSigWinch, null); } sigaction(SIGINT, &oldSigIntr, null); sigaction(SIGHUP, &oldHupIntr, null); } foreach_reverse(d; destructor) d(); } bool kbhit() { auto got = getch(true); if(got == dchar.init) return false; getchBuffer = got; return true; } bool timedCheckForInput(int milliseconds) { version(Windows) { auto response = WaitForSingleObject(terminal.hConsole, milliseconds); if(response == 0) return true; return false; } else version(Posix) { if(fdIn == -1) return false; timeval tv; tv.tv_sec = 0; tv.tv_usec = milliseconds * 1000; fd_set fs; FD_ZERO(&fs); FD_SET(fdIn, &fs); if(select(fdIn + 1, &fs, null, null, &tv) == -1) { return false; } return FD_ISSET(fdIn, &fs); } } bool anyInput_internal() { if(inputQueue.length || timedCheckForInput(0)) return true; version(Posix) if(interrupted || windowSizeChanged || hangedUp) return true; return false; } private dchar getchBuffer; dchar getch(bool nonblocking = false) { if(getchBuffer != dchar.init) { auto a = getchBuffer; getchBuffer = dchar.init; return a; } if(nonblocking && !anyInput_internal()) return dchar.init; auto event = nextEvent(); while(event.type != InputEvent.Type.KeyboardEvent || event.keyboardEvent.pressed == false) { if(event.type == InputEvent.Type.UserInterruptionEvent) throw new UserInterruptionException(); if(event.type == InputEvent.Type.HangupEvent) throw new HangupException(); if(event.type == InputEvent.Type.EndOfFileEvent) return dchar.init; if(nonblocking && !anyInput_internal()) return dchar.init; event = nextEvent(); } return event.keyboardEvent.which; } version(Posix) int nextRaw(bool interruptable = false) { if(fdIn == -1) return 0; char[1] buf; try_again: auto ret = read(fdIn, buf.ptr, buf.length); if(ret == 0) return 0; if(ret == -1) { import core.stdc.errno; if(errno == EINTR) if(interruptable) return -1; else goto try_again; else throw new Exception("read failed"); } if(ret == 1) return inputPrefilter ? inputPrefilter(buf[0]) : buf[0]; else assert(0); } version(Posix) int delegate(char) inputPrefilter; version(Posix) dchar nextChar(int starting) { if(starting <= 127) return cast(dchar) starting; char[6] buffer; int pos = 0; buffer[pos++] = cast(char) starting; int remaining = 0; ubyte magic = starting & 0xff; while(magic & 0b1000_000) { remaining++; magic <<= 1; } while(remaining && pos < buffer.length) { buffer[pos++] = cast(char) nextRaw(); remaining--; } import std.utf; size_t throwAway; return decode(buffer[], throwAway); } InputEvent checkWindowSizeChanged() { auto oldWidth = terminal.width; auto oldHeight = terminal.height; terminal.updateSize(); version(Posix) windowSizeChanged = false; return InputEvent(SizeChangedEvent(oldWidth, oldHeight, terminal.width, terminal.height), terminal); } InputEvent nextEvent() { terminal.flush(); if(inputQueue.length) { auto e = inputQueue[0]; inputQueue = inputQueue[1 .. $]; return e; } wait_for_more: version(Posix) if(interrupted) { interrupted = false; return InputEvent(UserInterruptionEvent(), terminal); } version(Posix) if(hangedUp) { hangedUp = false; return InputEvent(HangupEvent(), terminal); } version(Posix) if(windowSizeChanged) { return checkWindowSizeChanged(); } auto more = readNextEvents(); if(!more.length) goto wait_for_more; assert(more.length); auto e = more[0]; inputQueue = more[1 .. $]; return e; } InputEvent* peekNextEvent() { if(inputQueue.length) return &(inputQueue[0]); return null; } enum InjectionPosition { head, tail } void injectEvent(InputEvent ev, InjectionPosition where) { final switch(where) { case InjectionPosition.head: inputQueue = ev ~ inputQueue; break; case InjectionPosition.tail: inputQueue ~= ev; break; } } InputEvent[] inputQueue; version(Windows) InputEvent[] readNextEvents() { terminal.flush(); INPUT_RECORD[32] buffer; DWORD actuallyRead; auto success = ReadConsoleInputA(inputHandle, buffer.ptr, buffer.length, &actuallyRead); if(success == 0) throw new Exception("ReadConsoleInput"); InputEvent[] newEvents; input_loop: foreach(record; buffer[0 .. actuallyRead]) { switch(record.EventType) { case KEY_EVENT: auto ev = record.KeyEvent; KeyboardEvent ke; CharacterEvent e; NonCharacterKeyEvent ne; e.eventType = ev.bKeyDown ? CharacterEvent.Type.Pressed : CharacterEvent.Type.Released; ne.eventType = ev.bKeyDown ? NonCharacterKeyEvent.Type.Pressed : NonCharacterKeyEvent.Type.Released; ke.pressed = ev.bKeyDown ? true : false; if(!(flags & ConsoleInputFlags.releasedKeys) && !ev.bKeyDown) break; e.modifierState = ev.dwControlKeyState; ne.modifierState = ev.dwControlKeyState; ke.modifierState = ev.dwControlKeyState; if(ev.UnicodeChar) { ke.which = cast(dchar) cast(wchar) ev.UnicodeChar; newEvents ~= InputEvent(ke, terminal); e.character = cast(dchar) cast(wchar) ev.UnicodeChar; newEvents ~= InputEvent(e, terminal); } else { ne.key = cast(NonCharacterKeyEvent.Key) ev.wVirtualKeyCode; ke.which = cast(KeyboardEvent.Key) (ev.wVirtualKeyCode + 0xF0000); foreach(member; __traits(allMembers, NonCharacterKeyEvent.Key)) if(__traits(getMember, NonCharacterKeyEvent.Key, member) == ne.key) { newEvents ~= InputEvent(ke, terminal); newEvents ~= InputEvent(ne, terminal); break; } } break; case MOUSE_EVENT: auto ev = record.MouseEvent; MouseEvent e; e.modifierState = ev.dwControlKeyState; e.x = ev.dwMousePosition.X; e.y = ev.dwMousePosition.Y; switch(ev.dwEventFlags) { case 0: e.eventType = MouseEvent.Type.Pressed; static DWORD lastButtonState; auto lastButtonState2 = lastButtonState; e.buttons = ev.dwButtonState; lastButtonState = e.buttons; if(cast(DWORD) e.buttons < lastButtonState2) { e.eventType = MouseEvent.Type.Released; e.buttons = lastButtonState2 & ~e.buttons; } break; case MOUSE_MOVED: e.eventType = MouseEvent.Type.Moved; e.buttons = ev.dwButtonState; break; case 0x0004: e.eventType = MouseEvent.Type.Pressed; if(ev.dwButtonState > 0) e.buttons = MouseEvent.Button.ScrollDown; else e.buttons = MouseEvent.Button.ScrollUp; break; default: continue input_loop; } newEvents ~= InputEvent(e, terminal); break; case WINDOW_BUFFER_SIZE_EVENT: auto ev = record.WindowBufferSizeEvent; auto oldWidth = terminal.width; auto oldHeight = terminal.height; terminal._width = ev.dwSize.X; terminal._height = ev.dwSize.Y; newEvents ~= InputEvent(SizeChangedEvent(oldWidth, oldHeight, terminal.width, terminal.height), terminal); break; default: } } return newEvents; } version(Posix) InputEvent[] readNextEvents() { terminal.flush(); auto initial = readNextEventsHelper(); while(timedCheckForInput(0)) { auto ne = readNextEventsHelper(); initial ~= ne; foreach(n; ne) if(n.type == InputEvent.Type.EndOfFileEvent) return initial; } return initial; } version(Posix) InputEvent[] readNextEventsHelper() { InputEvent[] charPressAndRelease(dchar character) { if((flags & ConsoleInputFlags.releasedKeys)) return [ InputEvent(KeyboardEvent(true, character, 0), terminal), InputEvent(KeyboardEvent(false, character, 0), terminal), InputEvent(CharacterEvent(CharacterEvent.Type.Pressed, character, 0), terminal), InputEvent(CharacterEvent(CharacterEvent.Type.Released, character, 0), terminal), ]; else return [ InputEvent(KeyboardEvent(true, character, 0), terminal), InputEvent(CharacterEvent(CharacterEvent.Type.Pressed, character, 0), terminal) ]; } InputEvent[] keyPressAndRelease(NonCharacterKeyEvent.Key key, uint modifiers = 0) { if((flags & ConsoleInputFlags.releasedKeys)) return [ InputEvent(KeyboardEvent(true, cast(dchar)(key) + 0xF0000, modifiers), terminal), InputEvent(KeyboardEvent(false, cast(dchar)(key) + 0xF0000, modifiers), terminal), InputEvent(NonCharacterKeyEvent(NonCharacterKeyEvent.Type.Pressed, key, modifiers), terminal), InputEvent(NonCharacterKeyEvent(NonCharacterKeyEvent.Type.Released, key, modifiers), terminal), ]; else return [ InputEvent(KeyboardEvent(true, cast(dchar)(key) + 0xF0000, modifiers), terminal), InputEvent(NonCharacterKeyEvent(NonCharacterKeyEvent.Type.Pressed, key, modifiers), terminal) ]; } char[30] sequenceBuffer; char[] readEscapeSequence(char[] sequence) { int sequenceLength = 2; sequence[0] = '\033'; sequence[1] = '['; while(sequenceLength < sequence.length) { auto n = nextRaw(); sequence[sequenceLength++] = cast(char) n; if(n >= 0x40 && !(sequenceLength == 3 && n == '[')) break; } return sequence[0 .. sequenceLength]; } InputEvent[] translateTermcapName(string cap) { switch(cap) { case "k1": return keyPressAndRelease(NonCharacterKeyEvent.Key.F1); case "k2": return keyPressAndRelease(NonCharacterKeyEvent.Key.F2); case "k3": return keyPressAndRelease(NonCharacterKeyEvent.Key.F3); case "k4": return keyPressAndRelease(NonCharacterKeyEvent.Key.F4); case "k5": return keyPressAndRelease(NonCharacterKeyEvent.Key.F5); case "k6": return keyPressAndRelease(NonCharacterKeyEvent.Key.F6); case "k7": return keyPressAndRelease(NonCharacterKeyEvent.Key.F7); case "k8": return keyPressAndRelease(NonCharacterKeyEvent.Key.F8); case "k9": return keyPressAndRelease(NonCharacterKeyEvent.Key.F9); case "k;": case "k0": return keyPressAndRelease(NonCharacterKeyEvent.Key.F10); case "F1": return keyPressAndRelease(NonCharacterKeyEvent.Key.F11); case "F2": return keyPressAndRelease(NonCharacterKeyEvent.Key.F12); case "kb": return charPressAndRelease('\b'); case "kD": return keyPressAndRelease(NonCharacterKeyEvent.Key.Delete); case "kd": case "do": return keyPressAndRelease(NonCharacterKeyEvent.Key.DownArrow); case "ku": case "up": return keyPressAndRelease(NonCharacterKeyEvent.Key.UpArrow); case "kl": return keyPressAndRelease(NonCharacterKeyEvent.Key.LeftArrow); case "kr": case "nd": return keyPressAndRelease(NonCharacterKeyEvent.Key.RightArrow); case "kN": case "K5": return keyPressAndRelease(NonCharacterKeyEvent.Key.PageDown); case "kP": case "K2": return keyPressAndRelease(NonCharacterKeyEvent.Key.PageUp); case "ho": case "kh": case "K1": return keyPressAndRelease(NonCharacterKeyEvent.Key.Home); case "kH": return keyPressAndRelease(NonCharacterKeyEvent.Key.End); case "kI": return keyPressAndRelease(NonCharacterKeyEvent.Key.Insert); default: } return null; } InputEvent[] doEscapeSequence(in char[] sequence) { switch(sequence) { case "\033[200~": string data; for(;;) { auto n = nextRaw(); if(n == '\033') { n = nextRaw(); if(n == '[') { auto esc = readEscapeSequence(sequenceBuffer); if(esc == "\033[201~") { break; } else { data ~= esc; } } else { data ~= '\033'; data ~= cast(char) n; } } else { data ~= cast(char) n; } } return [InputEvent(PasteEvent(data), terminal)]; case "\033[M": auto buttonCode = nextRaw() - 32; auto x = cast(int) ((nextRaw())) - 33; auto y = cast(int) ((nextRaw())) - 33; bool isRelease = (buttonCode & 0b11) == 3; int buttonNumber; if(!isRelease) { buttonNumber = (buttonCode & 0b11); if(buttonCode & 64) buttonNumber += 3; buttonNumber++; if(buttonNumber == 2) buttonNumber = 3; else if(buttonNumber == 3) buttonNumber = 2; } auto modifiers = buttonCode & (0b0001_1100); MouseEvent m; if(buttonCode & 32) m.eventType = MouseEvent.Type.Moved; else m.eventType = isRelease ? MouseEvent.Type.Released : MouseEvent.Type.Pressed; static int buttonsDown = 0; if(!isRelease && buttonNumber <= 3) buttonsDown++; if(isRelease && m.eventType != MouseEvent.Type.Moved) { if(buttonsDown) buttonsDown--; else m.eventType = MouseEvent.Type.Moved; } if(buttonNumber == 0) m.buttons = 0; else m.buttons = 1 << (buttonNumber - 1); m.x = x; m.y = y; m.modifierState = modifiers; return [InputEvent(m, terminal)]; default: auto cap = terminal.findSequenceInTermcap(sequence); if(cap !is null) { return translateTermcapName(cap); } else { if(terminal.terminalInFamily("xterm")) { import std.conv, std.string; auto terminator = sequence[$ - 1]; auto parts = sequence[2 .. $ - 1].split(";"); uint modifierState; int modGot; if(parts.length > 1) modGot = to!int(parts[1]); mod_switch: switch(modGot) { case 2: modifierState |= ModifierState.shift; break; case 3: modifierState |= ModifierState.alt; break; case 4: modifierState |= ModifierState.shift | ModifierState.alt; break; case 5: modifierState |= ModifierState.control; break; case 6: modifierState |= ModifierState.shift | ModifierState.control; break; case 7: modifierState |= ModifierState.alt | ModifierState.control; break; case 8: modifierState |= ModifierState.shift | ModifierState.alt | ModifierState.control; break; case 9: .. case 16: modifierState |= ModifierState.meta; if(modGot != 9) { modGot -= 8; goto mod_switch; } break; case 20: .. case 36: modifierState |= ModifierState.windows; modGot -= 20; goto mod_switch; default: } switch(terminator) { case 'A': return keyPressAndRelease(NonCharacterKeyEvent.Key.UpArrow, modifierState); case 'B': return keyPressAndRelease(NonCharacterKeyEvent.Key.DownArrow, modifierState); case 'C': return keyPressAndRelease(NonCharacterKeyEvent.Key.RightArrow, modifierState); case 'D': return keyPressAndRelease(NonCharacterKeyEvent.Key.LeftArrow, modifierState); case 'H': return keyPressAndRelease(NonCharacterKeyEvent.Key.Home, modifierState); case 'F': return keyPressAndRelease(NonCharacterKeyEvent.Key.End, modifierState); case 'P': return keyPressAndRelease(NonCharacterKeyEvent.Key.F1, modifierState); case 'Q': return keyPressAndRelease(NonCharacterKeyEvent.Key.F2, modifierState); case 'R': return keyPressAndRelease(NonCharacterKeyEvent.Key.F3, modifierState); case 'S': return keyPressAndRelease(NonCharacterKeyEvent.Key.F4, modifierState); case '~': switch(parts[0]) { case "5": return keyPressAndRelease(NonCharacterKeyEvent.Key.PageUp, modifierState); case "6": return keyPressAndRelease(NonCharacterKeyEvent.Key.PageDown, modifierState); case "2": return keyPressAndRelease(NonCharacterKeyEvent.Key.Insert, modifierState); case "3": return keyPressAndRelease(NonCharacterKeyEvent.Key.Delete, modifierState); case "15": return keyPressAndRelease(NonCharacterKeyEvent.Key.F5, modifierState); case "17": return keyPressAndRelease(NonCharacterKeyEvent.Key.F6, modifierState); case "18": return keyPressAndRelease(NonCharacterKeyEvent.Key.F7, modifierState); case "19": return keyPressAndRelease(NonCharacterKeyEvent.Key.F8, modifierState); case "20": return keyPressAndRelease(NonCharacterKeyEvent.Key.F9, modifierState); case "21": return keyPressAndRelease(NonCharacterKeyEvent.Key.F10, modifierState); case "23": return keyPressAndRelease(NonCharacterKeyEvent.Key.F11, modifierState); case "24": return keyPressAndRelease(NonCharacterKeyEvent.Key.F12, modifierState); default: } break; default: } } else if(terminal.terminalInFamily("rxvt")) { } else { } } } return null; } auto c = nextRaw(true); if(c == -1) return null; if(c == 0) return [InputEvent(EndOfFileEvent(), terminal)]; if(c == '\033') { if(timedCheckForInput(50)) { c = nextRaw(); if(c == '[') { return doEscapeSequence(readEscapeSequence(sequenceBuffer)); } else if(c == 'O') { auto n = nextRaw(); char[3] thing; thing[0] = '\033'; thing[1] = 'O'; thing[2] = cast(char) n; auto cap = terminal.findSequenceInTermcap(thing); if(cap is null) { return keyPressAndRelease(NonCharacterKeyEvent.Key.escape) ~ charPressAndRelease('O') ~ charPressAndRelease(thing[2]); } else { return translateTermcapName(cap); } } else { return keyPressAndRelease(NonCharacterKeyEvent.Key.escape) ~ charPressAndRelease(nextChar(c)); } } else { return keyPressAndRelease(NonCharacterKeyEvent.Key.escape); } } else { auto next = nextChar(c); if(next == 127) next = '\b'; return charPressAndRelease(next); } } } struct KeyboardEvent { bool pressed; dchar which; uint modifierState; bool isCharacter() { return !(which >= Key.min && which <= Key.max); } enum Key : dchar { escape = 0x1b + 0xF0000, F1 = 0x70 + 0xF0000, F2 = 0x71 + 0xF0000, F3 = 0x72 + 0xF0000, F4 = 0x73 + 0xF0000, F5 = 0x74 + 0xF0000, F6 = 0x75 + 0xF0000, F7 = 0x76 + 0xF0000, F8 = 0x77 + 0xF0000, F9 = 0x78 + 0xF0000, F10 = 0x79 + 0xF0000, F11 = 0x7A + 0xF0000, F12 = 0x7B + 0xF0000, LeftArrow = 0x25 + 0xF0000, RightArrow = 0x27 + 0xF0000, UpArrow = 0x26 + 0xF0000, DownArrow = 0x28 + 0xF0000, Insert = 0x2d + 0xF0000, Delete = 0x2e + 0xF0000, Home = 0x24 + 0xF0000, End = 0x23 + 0xF0000, PageUp = 0x21 + 0xF0000, PageDown = 0x22 + 0xF0000, } } struct CharacterEvent { enum Type { Released, Pressed } Type eventType; dchar character; uint modifierState; } struct NonCharacterKeyEvent { enum Type { Released, Pressed } Type eventType; enum Key : int { escape = 0x1b, F1 = 0x70, F2 = 0x71, F3 = 0x72, F4 = 0x73, F5 = 0x74, F6 = 0x75, F7 = 0x76, F8 = 0x77, F9 = 0x78, F10 = 0x79, F11 = 0x7A, F12 = 0x7B, LeftArrow = 0x25, RightArrow = 0x27, UpArrow = 0x26, DownArrow = 0x28, Insert = 0x2d, Delete = 0x2e, Home = 0x24, End = 0x23, PageUp = 0x21, PageDown = 0x22, } Key key; uint modifierState; } struct PasteEvent { string pastedText; } struct MouseEvent { enum Type { Moved = 0, Pressed = 1, Released = 2, Clicked, } Type eventType; enum Button : uint { None = 0, Left = 1, Middle = 4, Right = 2, ScrollUp = 8, ScrollDown = 16 } uint buttons; int x; int y; uint modifierState; } struct SizeChangedEvent { int oldWidth; int oldHeight; int newWidth; int newHeight; } struct UserInterruptionEvent {} struct HangupEvent {} struct EndOfFileEvent {} interface CustomEvent {} version(Windows) enum ModifierState : uint { shift = 0x10, control = 0x8 | 0x4, alt = 2 | 1, windows = 512, meta = 4096, scrollLock = 0x40, } else enum ModifierState : uint { shift = 4, alt = 2, control = 16, meta = 8, windows = 512 } version(DDoc) enum ModifierState : uint { shift = 4, alt = 2, control = 16, } struct InputEvent { enum Type { KeyboardEvent, CharacterEvent, NonCharacterKeyEvent, PasteEvent, MouseEvent, SizeChangedEvent, UserInterruptionEvent, EndOfFileEvent, HangupEvent, CustomEvent } @property Type type() { return t; } @property Terminal* terminal() { return term; } @property auto get(Type T)() { if(type != T) throw new Exception("Wrong event type"); static if(T == Type.CharacterEvent) return characterEvent; else static if(T == Type.KeyboardEvent) return keyboardEvent; else static if(T == Type.NonCharacterKeyEvent) return nonCharacterKeyEvent; else static if(T == Type.PasteEvent) return pasteEvent; else static if(T == Type.MouseEvent) return mouseEvent; else static if(T == Type.SizeChangedEvent) return sizeChangedEvent; else static if(T == Type.UserInterruptionEvent) return userInterruptionEvent; else static if(T == Type.EndOfFileEvent) return endOfFileEvent; else static if(T == Type.HangupEvent) return hangupEvent; else static if(T == Type.CustomEvent) return customEvent; else static assert(0, "Type " ~ T.stringof ~ " not added to the get function"); } this(CustomEvent c, Terminal* p = null) { t = Type.CustomEvent; customEvent = c; } private { this(CharacterEvent c, Terminal* p) { t = Type.CharacterEvent; characterEvent = c; } this(KeyboardEvent c, Terminal* p) { t = Type.KeyboardEvent; keyboardEvent = c; } this(NonCharacterKeyEvent c, Terminal* p) { t = Type.NonCharacterKeyEvent; nonCharacterKeyEvent = c; } this(PasteEvent c, Terminal* p) { t = Type.PasteEvent; pasteEvent = c; } this(MouseEvent c, Terminal* p) { t = Type.MouseEvent; mouseEvent = c; } this(SizeChangedEvent c, Terminal* p) { t = Type.SizeChangedEvent; sizeChangedEvent = c; } this(UserInterruptionEvent c, Terminal* p) { t = Type.UserInterruptionEvent; userInterruptionEvent = c; } this(HangupEvent c, Terminal* p) { t = Type.HangupEvent; hangupEvent = c; } this(EndOfFileEvent c, Terminal* p) { t = Type.EndOfFileEvent; endOfFileEvent = c; } Type t; Terminal* term; union { KeyboardEvent keyboardEvent; CharacterEvent characterEvent; NonCharacterKeyEvent nonCharacterKeyEvent; PasteEvent pasteEvent; MouseEvent mouseEvent; SizeChangedEvent sizeChangedEvent; UserInterruptionEvent userInterruptionEvent; HangupEvent hangupEvent; EndOfFileEvent endOfFileEvent; CustomEvent customEvent; } } } class LineGetter { string[] history; Terminal* terminal; string historyFilename; this(Terminal* tty, string historyFilename = null) { this.terminal = tty; this.historyFilename = historyFilename; line.reserve(128); if(historyFilename.length) loadSettingsAndHistoryFromFile(); regularForeground = cast(Color) terminal._currentForeground; background = cast(Color) terminal._currentBackground; suggestionForeground = Color.blue; } void dispose() { if(historyFilename.length) saveSettingsAndHistoryToFile(); } string historyFileDirectory() { version(Windows) { char[1024] path; if(0) { import core.stdc.string; return cast(string) path[0 .. strlen(path.ptr)] ~ "\\arsd-getline"; } else { import std.process; return environment["APPDATA"] ~ "\\arsd-getline"; } } else version(Posix) { import std.process; return environment["HOME"] ~ "/.arsd-getline"; } } Color suggestionForeground; Color regularForeground; Color background; string prompt; bool autoSuggest = true; string historyFilter(string candidate) { return candidate; } void saveSettingsAndHistoryToFile() { import std.file; if(!exists(historyFileDirectory)) mkdir(historyFileDirectory); auto fn = historyPath(); import std.stdio; auto file = File(fn, "wt"); foreach(item; history) file.writeln(item); } private string historyPath() { import std.path; auto filename = historyFileDirectory() ~ dirSeparator ~ historyFilename ~ ".history"; return filename; } void loadSettingsAndHistoryFromFile() { import std.file; history = null; auto fn = historyPath(); if(exists(fn)) { import std.stdio; foreach(line; File(fn, "rt").byLine) history ~= line.idup; } } protected string[] tabComplete(in dchar[] candidate) { return history.length > 20 ? history[0 .. 20] : history; } private string[] filterTabCompleteList(string[] list) { if(list.length == 0) return list; string[] f; f.reserve(list.length); foreach(item; list) { import std.algorithm; if(startsWith(item, line[0 .. cursorPosition])) f ~= item; } return f; } protected void showTabCompleteList(string[] list) { if(list.length) { terminal.writeln(); foreach(item; list) { terminal.color(suggestionForeground, background); import std.utf; auto idx = codeLength!char(line[0 .. cursorPosition]); terminal.write(" ", item[0 .. idx]); terminal.color(regularForeground, background); terminal.writeln(item[idx .. $]); } updateCursorPosition(); redraw(); } } public string getline(RealTimeConsoleInput* input = null) { startGettingLine(); if(input is null) { auto i = RealTimeConsoleInput(terminal, ConsoleInputFlags.raw | ConsoleInputFlags.allInputEvents); while(workOnLine(i.nextEvent())) {} } else while(workOnLine(input.nextEvent())) {} return finishGettingLine(); } private int currentHistoryViewPosition = 0; private dchar[] uncommittedHistoryCandidate; void loadFromHistory(int howFarBack) { if(howFarBack < 0) howFarBack = 0; if(howFarBack > history.length) howFarBack = cast(int) history.length; if(howFarBack == currentHistoryViewPosition) return; if(currentHistoryViewPosition == 0) { if(uncommittedHistoryCandidate.length < line.length) { uncommittedHistoryCandidate.length = line.length; } uncommittedHistoryCandidate[0 .. line.length] = line[]; uncommittedHistoryCandidate = uncommittedHistoryCandidate[0 .. line.length]; uncommittedHistoryCandidate.assumeSafeAppend(); } currentHistoryViewPosition = howFarBack; if(howFarBack == 0) { line.length = uncommittedHistoryCandidate.length; line.assumeSafeAppend(); line[] = uncommittedHistoryCandidate[]; } else { line = line[0 .. 0]; line.assumeSafeAppend(); foreach(dchar ch; history[$ - howFarBack]) line ~= ch; } cursorPosition = cast(int) line.length; scrollToEnd(); } bool insertMode = true; bool multiLineMode = false; private dchar[] line; private int cursorPosition = 0; private int horizontalScrollPosition = 0; private void scrollToEnd() { horizontalScrollPosition = (cast(int) line.length); horizontalScrollPosition -= availableLineLength(); if(horizontalScrollPosition < 0) horizontalScrollPosition = 0; } private int startOfLineX; private int startOfLineY; private string suggestion(string[] list = null) { import std.algorithm, std.utf; auto relevantLineSection = line[0 .. cursorPosition]; if(list is null) list = filterTabCompleteList(tabComplete(relevantLineSection)); if(list.length) { string commonality = list[0]; foreach(item; list[1 .. $]) { commonality = commonPrefix(commonality, item); } if(commonality.length) { return commonality[codeLength!char(relevantLineSection) .. $]; } } return null; } void addChar(dchar ch) { assert(cursorPosition >= 0 && cursorPosition <= line.length); if(cursorPosition == line.length) line ~= ch; else { assert(line.length); if(insertMode) { line ~= ' '; for(int i = cast(int) line.length - 2; i >= cursorPosition; i --) line[i + 1] = line[i]; } line[cursorPosition] = ch; } cursorPosition++; if(cursorPosition >= horizontalScrollPosition + availableLineLength()) horizontalScrollPosition++; } void addString(string s) { foreach(dchar ch; s) addChar(ch); } void deleteChar() { if(cursorPosition == line.length) return; for(int i = cursorPosition; i < line.length - 1; i++) line[i] = line[i + 1]; line = line[0 .. $-1]; line.assumeSafeAppend(); } void deleteToEndOfLine() { while(cursorPosition < line.length) deleteChar(); } int availableLineLength() { return terminal.width - startOfLineX - cast(int) prompt.length - 1; } private int lastDrawLength = 0; void redraw() { terminal.moveTo(startOfLineX, startOfLineY); auto lineLength = availableLineLength(); if(lineLength < 0) throw new Exception("too narrow terminal to draw"); terminal.write(prompt); auto towrite = line[horizontalScrollPosition .. $]; auto cursorPositionToDrawX = cursorPosition - horizontalScrollPosition; auto cursorPositionToDrawY = 0; if(towrite.length > lineLength) { towrite = towrite[0 .. lineLength]; } terminal.write(towrite); lineLength -= towrite.length; string suggestion; if(lineLength >= 0) { suggestion = ((cursorPosition == towrite.length) && autoSuggest) ? this.suggestion() : null; if(suggestion.length) { terminal.color(suggestionForeground, background); terminal.write(suggestion); terminal.color(regularForeground, background); } } auto written = cast(int) (towrite.length + suggestion.length + prompt.length); if(written < lastDrawLength) foreach(i; written .. lastDrawLength) terminal.write(" "); lastDrawLength = written; terminal.moveTo(startOfLineX + cursorPositionToDrawX + cast(int) prompt.length, startOfLineY + cursorPositionToDrawY); } void startGettingLine() { cursorPosition = 0; horizontalScrollPosition = 0; justHitTab = false; currentHistoryViewPosition = 0; if(line.length) { line = line[0 .. 0]; line.assumeSafeAppend(); } updateCursorPosition(); terminal.showCursor(); lastDrawLength = availableLineLength(); redraw(); } private void updateCursorPosition() { terminal.flush(); version(Windows) { CONSOLE_SCREEN_BUFFER_INFO info; GetConsoleScreenBufferInfo(terminal.hConsole, &info); startOfLineX = info.dwCursorPosition.X; startOfLineY = info.dwCursorPosition.Y; } else { ubyte[128] hack2; termios old; ubyte[128] hack; tcgetattr(terminal.fdIn, &old); auto n = old; n.c_lflag &= ~(ICANON | ECHO); tcsetattr(terminal.fdIn, TCSANOW, &n); scope(exit) tcsetattr(terminal.fdIn, TCSANOW, &old); terminal.writeStringRaw("\033[6n"); terminal.flush(); import core.sys.posix.unistd; ubyte[16] buffer; auto len = read(terminal.fdIn, buffer.ptr, buffer.length); if(len <= 0) throw new Exception("Couldn't get cursor position to initialize get line"); auto got = buffer[0 .. len]; if(got.length < 6) throw new Exception("not enough cursor reply answer"); if(got[0] != '\033' || got[1] != '[' || got[$-1] != 'R') throw new Exception("wrong answer for cursor position"); auto gots = cast(char[]) got[2 .. $-1]; import std.conv; import std.string; auto pieces = split(gots, ";"); if(pieces.length != 2) throw new Exception("wtf wrong answer on cursor position"); startOfLineX = to!int(pieces[1]) - 1; startOfLineY = to!int(pieces[0]) - 1; } terminal._cursorX = startOfLineX; terminal._cursorY = startOfLineY; } private bool justHitTab; bool workOnLine(InputEvent e) { switch(e.type) { case InputEvent.Type.EndOfFileEvent: justHitTab = false; return false; case InputEvent.Type.KeyboardEvent: auto ev = e.keyboardEvent; if(ev.pressed == false) return true; auto ch = ev.which; switch(ch) { case 4: case '\r': case '\n': justHitTab = false; return false; case '\t': auto relevantLineSection = line[0 .. cursorPosition]; auto possibilities = filterTabCompleteList(tabComplete(relevantLineSection)); import std.utf; if(possibilities.length == 1) { auto toFill = possibilities[0][codeLength!char(relevantLineSection) .. $]; if(toFill.length) { addString(toFill); redraw(); } justHitTab = false; } else { if(justHitTab) { justHitTab = false; showTabCompleteList(possibilities); } else { justHitTab = true; auto suggestion = this.suggestion(possibilities); if(suggestion.length) { addString(suggestion); redraw(); } } } break; case '\b': justHitTab = false; if(cursorPosition) { cursorPosition--; for(int i = cursorPosition; i < line.length - 1; i++) line[i] = line[i + 1]; line = line[0 .. $ - 1]; line.assumeSafeAppend(); if(!multiLineMode) { if(horizontalScrollPosition > cursorPosition - 1) horizontalScrollPosition = cursorPosition - 1 - availableLineLength(); if(horizontalScrollPosition < 0) horizontalScrollPosition = 0; } redraw(); } break; case KeyboardEvent.Key.LeftArrow: justHitTab = false; if(cursorPosition) cursorPosition--; if(!multiLineMode) { if(cursorPosition < horizontalScrollPosition) horizontalScrollPosition--; } redraw(); break; case KeyboardEvent.Key.RightArrow: justHitTab = false; if(cursorPosition < line.length) cursorPosition++; if(!multiLineMode) { if(cursorPosition >= horizontalScrollPosition + availableLineLength()) horizontalScrollPosition++; } redraw(); break; case KeyboardEvent.Key.UpArrow: justHitTab = false; loadFromHistory(currentHistoryViewPosition + 1); redraw(); break; case KeyboardEvent.Key.DownArrow: justHitTab = false; loadFromHistory(currentHistoryViewPosition - 1); redraw(); break; case KeyboardEvent.Key.PageUp: justHitTab = false; loadFromHistory(cast(int) history.length); redraw(); break; case KeyboardEvent.Key.PageDown: justHitTab = false; loadFromHistory(0); redraw(); break; case 1: case KeyboardEvent.Key.Home: justHitTab = false; cursorPosition = 0; horizontalScrollPosition = 0; redraw(); break; case 5: case KeyboardEvent.Key.End: justHitTab = false; cursorPosition = cast(int) line.length; scrollToEnd(); redraw(); break; case KeyboardEvent.Key.Insert: justHitTab = false; insertMode = !insertMode; break; case KeyboardEvent.Key.Delete: justHitTab = false; if(ev.modifierState & ModifierState.control) deleteToEndOfLine(); else deleteChar(); redraw(); break; case 11: justHitTab = false; deleteToEndOfLine(); redraw(); break; default: justHitTab = false; if(e.keyboardEvent.isCharacter) addChar(ch); redraw(); } break; case InputEvent.Type.PasteEvent: justHitTab = false; addString(e.pasteEvent.pastedText); redraw(); break; case InputEvent.Type.MouseEvent: auto me = e.mouseEvent; if(me.eventType == MouseEvent.Type.Pressed) { if(me.buttons & MouseEvent.Button.Left) { if(me.y == startOfLineY) { int p = me.x - startOfLineX - cast(int) prompt.length + horizontalScrollPosition; if(p >= 0 && p < line.length) { justHitTab = false; cursorPosition = p; redraw(); } } } } break; case InputEvent.Type.SizeChangedEvent: break; case InputEvent.Type.UserInterruptionEvent: throw new UserInterruptionException(); case InputEvent.Type.HangupEvent: throw new HangupException(); default: } return true; } string finishGettingLine() { import std.conv; auto f = to!string(line); auto history = historyFilter(f); if(history !is null) this.history ~= history; return f; } } mixin template LineGetterConstructors() { this(Terminal* tty, string historyFilename = null) { super(tty, historyFilename); } } class FileLineGetter : LineGetter { mixin LineGetterConstructors; string searchDirectory = "."; override protected string[] tabComplete(in dchar[] candidate) { import std.file, std.conv, std.algorithm, std.string; const(dchar)[] soFar = candidate; auto idx = candidate.lastIndexOf(" "); if(idx != -1) soFar = candidate[idx + 1 .. $]; string[] list; foreach(string name; dirEntries(searchDirectory, SpanMode.breadth)) { if(startsWith(name[2..$], soFar)) list ~= text(candidate, name[searchDirectory.length + 1 + soFar.length .. $]); else if(startsWith(name, soFar)) list ~= text(candidate, name[soFar.length .. $]); } return list; } } version(Windows) { enum CSIDL_APPDATA = 26; extern(Windows) HRESULT SHGetFolderPathA(HWND, int, HANDLE, DWORD, LPSTR); } struct ScrollbackBuffer { bool demandsAttention; this(string name) { this.name = name; } void write(T...)(T t) { import std.conv : text; addComponent(text(t), foreground_, background_, null); } void writeln(T...)(T t) { write(t, "\n"); } void writef(T...)(string fmt, T t) { import std.format: format; write(format(fmt, t)); } void writefln(T...)(string fmt, T t) { writef(fmt, t, "\n"); } void clear() { lines = null; clickRegions = null; scrollbackPosition = 0; } int foreground_ = Color.DEFAULT, background_ = Color.DEFAULT; void color(int foreground, int background) { this.foreground_ = foreground; this.background_ = background; } void addComponent(string text, int foreground, int background, bool delegate() onclick) { if(lines.length == 0) { addLine(); } bool first = true; import std.algorithm; foreach(t; splitter(text, "\n")) { if(!first) addLine(); first = false; lines[$-1].components ~= LineComponent(t, foreground, background, onclick); } } void addLine() { lines ~= Line(); if(scrollbackPosition) scrollbackPosition++; } void addLine(string line) { lines ~= Line([LineComponent(line)]); if(scrollbackPosition) scrollbackPosition++; } void scrollUp(int lines = 1) { scrollbackPosition += lines; } void scrollDown(int lines = 1) { scrollbackPosition -= lines; if(scrollbackPosition < 0) scrollbackPosition = 0; } void scrollToBottom() { scrollbackPosition = 0; } void scrollToTop(int width, int height) { scrollbackPosition = scrollTopPosition(width, height); } struct LineComponent { string text; bool isRgb; union { int color; RGB colorRgb; } union { int background; RGB backgroundRgb; } bool delegate() onclick; this(string text, int color = Color.DEFAULT, int background = Color.DEFAULT, bool delegate() onclick = null) { this.text = text; this.color = color; this.background = background; this.onclick = onclick; this.isRgb = false; } this(string text, RGB colorRgb, RGB backgroundRgb = RGB(0, 0, 0), bool delegate() onclick = null) { this.text = text; this.colorRgb = colorRgb; this.backgroundRgb = backgroundRgb; this.onclick = onclick; this.isRgb = true; } } struct Line { LineComponent[] components; int length() { int l = 0; foreach(c; components) l += c.text.length; return l; } } Line[] lines; string name; int x, y, width, height; int scrollbackPosition; int scrollTopPosition(int width, int height) { int lineCount; foreach_reverse(line; lines) { int written = 0; comp_loop: foreach(cidx, component; line.components) { auto towrite = component.text; foreach(idx, dchar ch; towrite) { if(written >= width) { lineCount++; written = 0; } if(ch == '\t') written += 8; else written++; } } lineCount++; } return lineCount - height; } void drawInto(Terminal* terminal, in int x = 0, in int y = 0, int width = 0, int height = 0) { if(lines.length == 0) return; if(width == 0) width = terminal.width; if(height == 0) height = terminal.height; this.x = x; this.y = y; this.width = width; this.height = height; int remaining = height + scrollbackPosition; int start = cast(int) lines.length; int howMany = 0; bool firstPartial = false; static struct Idx { size_t cidx; size_t idx; } Idx firstPartialStartIndex; clickRegions.length = 0; clickRegions.assumeSafeAppend(); foreach_reverse(line; lines) { int written = 0; int brokenLineCount; Idx[16] lineBreaksBuffer; Idx[] lineBreaks = lineBreaksBuffer[]; comp_loop: foreach(cidx, component; line.components) { auto towrite = component.text; foreach(idx, dchar ch; towrite) { if(written >= width) { if(brokenLineCount == lineBreaks.length) lineBreaks ~= Idx(cidx, idx); else lineBreaks[brokenLineCount] = Idx(cidx, idx); brokenLineCount++; written = 0; } if(ch == '\t') written += 8; else written++; } } lineBreaks = lineBreaks[0 .. brokenLineCount]; foreach_reverse(lineBreak; lineBreaks) { if(remaining == 1) { firstPartial = true; firstPartialStartIndex = lineBreak; break; } else { remaining--; } if(remaining <= 0) break; } remaining--; start--; howMany++; if(remaining <= 0) break; } int linePos = remaining; foreach(idx, line; lines[start .. start + howMany]) { int written = 0; if(linePos < 0) { linePos++; continue; } terminal.moveTo(x, y + ((linePos >= 0) ? linePos : 0)); auto todo = line.components; if(firstPartial) { todo = todo[firstPartialStartIndex.cidx .. $]; } foreach(ref component; todo) { if(component.isRgb) terminal.setTrueColor(component.colorRgb, component.backgroundRgb); else terminal.color(component.color, component.background); auto towrite = component.text; again: if(linePos >= height) break; if(firstPartial) { towrite = towrite[firstPartialStartIndex.idx .. $]; firstPartial = false; } foreach(idx, dchar ch; towrite) { if(written >= width) { clickRegions ~= ClickRegion(&component, terminal.cursorX, terminal.cursorY, written); terminal.write(towrite[0 .. idx]); towrite = towrite[idx .. $]; linePos++; written = 0; terminal.moveTo(x, y + linePos); goto again; } if(ch == '\t') written += 8; else written++; } if(towrite.length) { clickRegions ~= ClickRegion(&component, terminal.cursorX, terminal.cursorY, written); terminal.write(towrite); } } if(written < width) { terminal.color(Color.DEFAULT, Color.DEFAULT); foreach(i; written .. width) terminal.write(" "); } linePos++; if(linePos >= height) break; } if(linePos < height) { terminal.color(Color.DEFAULT, Color.DEFAULT); foreach(i; linePos .. height) { if(i >= 0 && i < height) { terminal.moveTo(x, y + i); foreach(w; 0 .. width) terminal.write(" "); } } } } private struct ClickRegion { LineComponent* component; int xStart; int yStart; int length; } private ClickRegion[] clickRegions; bool handleEvent(InputEvent e) { final switch(e.type) { case InputEvent.Type.KeyboardEvent: auto ev = e.keyboardEvent; demandsAttention = false; switch(ev.which) { case KeyboardEvent.Key.UpArrow: scrollUp(); return true; case KeyboardEvent.Key.DownArrow: scrollDown(); return true; case KeyboardEvent.Key.PageUp: scrollUp(height); return true; case KeyboardEvent.Key.PageDown: scrollDown(height); return true; default: } break; case InputEvent.Type.MouseEvent: auto ev = e.mouseEvent; if(ev.x >= x && ev.x < x + width && ev.y >= y && ev.y < y + height) { demandsAttention = false; auto mx = ev.x - x; auto my = ev.y - y; if(ev.eventType == MouseEvent.Type.Pressed) { if(ev.buttons & MouseEvent.Button.Left) { foreach(region; clickRegions) if(ev.x >= region.xStart && ev.x < region.xStart + region.length && ev.y == region.yStart) if(region.component.onclick !is null) return region.component.onclick(); } if(ev.buttons & MouseEvent.Button.ScrollUp) { scrollUp(); return true; } if(ev.buttons & MouseEvent.Button.ScrollDown) { scrollDown(); return true; } } } else { } break; case InputEvent.Type.SizeChangedEvent: return true; case InputEvent.Type.UserInterruptionEvent: throw new UserInterruptionException(); case InputEvent.Type.HangupEvent: throw new HangupException(); case InputEvent.Type.EndOfFileEvent: break; case InputEvent.Type.CharacterEvent: case InputEvent.Type.NonCharacterKeyEvent: break; case InputEvent.Type.CustomEvent: case InputEvent.Type.PasteEvent: break; } return false; } } class UserInterruptionException : Exception { this() { super("Ctrl+C"); } } class HangupException : Exception { this() { super("Hup"); } } ubyte colorToXTermPaletteIndex(RGB color) { if(color.r == color.g && color.g == color.b) { if(color.r == 0) return 0; if(color.r >= 248) return 15; return cast(ubyte) (232 + ((color.r - 8) / 10)); } auto r = (cast(int) color.r - 35) / 40; auto g = (cast(int) color.g - 35) / 40; auto b = (cast(int) color.b - 35) / 40; return cast(ubyte) (16 + b + g*6 + r*36); } struct RGB { ubyte r; ubyte g; ubyte b; private ubyte a = 255; } RGB xtermPaletteIndexToColor(int paletteIdx) { RGB color; if(paletteIdx < 16) { if(paletteIdx == 7) return RGB(0xc0, 0xc0, 0xc0); else if(paletteIdx == 8) return RGB(0x80, 0x80, 0x80); color.r = (paletteIdx & 0b001) ? ((paletteIdx & 0b1000) ? 0xff : 0x80) : 0x00; color.g = (paletteIdx & 0b010) ? ((paletteIdx & 0b1000) ? 0xff : 0x80) : 0x00; color.b = (paletteIdx & 0b100) ? ((paletteIdx & 0b1000) ? 0xff : 0x80) : 0x00; } else if(paletteIdx < 232) { color.r = cast(ubyte) ((paletteIdx - 16) / 36 * 40 + 55); color.g = cast(ubyte) (((paletteIdx - 16) % 36) / 6 * 40 + 55); color.b = cast(ubyte) ((paletteIdx - 16) % 6 * 40 + 55); if(color.r == 55) color.r = 0; if(color.g == 55) color.g = 0; if(color.b == 55) color.b = 0; } else { color.r = cast(ubyte) (8 + (paletteIdx - 232) * 10); color.g = color.r; color.b = color.g; } return color; } int approximate16Color(RGB color) { int c; c |= color.r > 64 ? RED_BIT : 0; c |= color.g > 64 ? GREEN_BIT : 0; c |= color.b > 64 ? BLUE_BIT : 0; c |= (((color.r + color.g + color.b) / 3) > 80) ? Bright : 0; return c; }