-
Notifications
You must be signed in to change notification settings - Fork 17
/
Copy pathflexprompt_modules.lua
2387 lines (2080 loc) · 86.5 KB
/
flexprompt_modules.lua
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
--------------------------------------------------------------------------------
-- Built in modules for flexprompt.
if ((clink and clink.version_encoded) or 0) < 10020010 then
return
end
--------------------------------------------------------------------------------
-- Internals.
-- luacheck: no max line length
-- luacheck: globals console os
-- luacheck: globals flexprompt
if not flexprompt or not flexprompt.add_module or not settings.get("flexprompt.enable") then
return
end
-- Is reset to {} at each onbeginedit.
local _cached_state = {}
local mod_brightcyan = { fg="96", bg="106", extfg="38;5;44", extbg="48;5;44" }
local mod_cyan = { fg="36", bg="46", extfg="38;5;6", extbg="48;5;6", lean=mod_brightcyan, classic=mod_brightcyan }
flexprompt.add_color("mod_cyan", mod_cyan)
local keymap_bright = { fg="94", bg="104", extfg="38;5;111", extbg="48;5;111" }
local keymap_color = { fg="34", bg="44", extfg="38;5;26", extbg="48;5;26", lean=keymap_bright, classic=keymap_bright }
flexprompt.add_color("keymap", keymap_color)
--------------------------------------------------------------------------------
-- Helpers.
-- Expects the colors arg to follow this scheme:
-- All elements are by index:
-- 1 = token
-- 2 = alttoken
-- 3 = color
-- 4 = altcolor
-- 5 = extended color
-- 6 = extended altcolor
local function parse_color_token(args, colors)
local parsed_colors = flexprompt.parse_arg_token(args, colors[1], colors[2])
local color = flexprompt.use_best_color(colors[3], colors[5] or colors[3])
local altcolor = flexprompt.use_best_color(colors[4], colors[6] or colors[4])
color, altcolor = flexprompt.parse_colors(parsed_colors, color, altcolor)
return color, altcolor
end
--------------------------------------------------------------------------------
-- ADMIN MODULE: {admin:always:forcetext:text_options:icon_options:color_options}
-- - 'always' shows the module even when not running as an Administrator.
-- - 'forcetext' shows text even if an icon is shown.
-- - 'admintext=abc' sets admin mode text to 'abc' ('admintext=' for none).
-- - 'normaltext=xyz' sets normal mode text to 'xyz' ('normaltext=' for none).
-- - 'adminicon=X' sets the admin mode icon to 'X'.
-- - 'normalicon=Y' sets the normal mode icon to 'Y'.
-- - color_options override status colors as follows:
-- - normal=color_name,alt_color_name When not running as an Administrator.
-- - admin=color_name,alt_color_name When running as an Administrator.
--
-- By default, it uses just an icon if icons are enabled and the font supports
-- powerline characters.
--
-- NOTES:
-- - The ADMIN module requires at least Clink v1.4.17 or higher.
-- - The admintext and normaltext options currently don't support text that
-- contains a colon character ':'.
local admin_color_list =
{
default = {
-- NOTE: Weird... Using "38;2;0;102;0" here makes Windows Terminal
-- fail to draw color emoji later on in the prompt line. And yet it
-- doesn't cause a problem when the style is "rainbow".
no_admin = { "n", "normal", "green", nil, "38;5;28", nil },
admin = { "a", "admin", "brightred", nil, "38;5;203", nil },
},
rainbow = {
no_admin = { "n", "normal", "green", "white", "38;2;0;48;12", "38;5;252" },
admin = { "a", "admin", "red", "brightyellow", "38;2;64;0;0", "38;5;203" },
},
}
local admin_text_list =
{
no_admin = "normal",
admin = "ADMIN",
}
local function render_admin(args)
local is_admin = _cached_state.is_admin or os.isuseradmin()
local mode = is_admin and "admin" or "no_admin"
if not is_admin then
local always = flexprompt.parse_arg_keyword(args, "always")
if not always then
return
end
end
local forcetext = flexprompt.parse_arg_keyword(args, "forcetext")
local colorlist = admin_color_list[flexprompt.get_style()] or admin_color_list.default
local colors = colorlist[mode]
local fallbacksymbol
local icon = flexprompt.parse_arg_token(args, is_admin and "ai" or "ni", is_admin and "adminicon" or "normalicon") or ""
if icon == "" then
icon = flexprompt.get_symbol(mode) or ""
fallbacksymbol = true
end
local text = ""
if forcetext or icon == "" then
text = flexprompt.parse_arg_token(args, is_admin and "at" or "nt", is_admin and "admintext" or "normaltext") or ""
if text == "" then
text = admin_text_list[mode]
end
end
if text ~= "" and fallbacksymbol then
icon = flexprompt.get_icon(mode) or ""
end
text = flexprompt.append_text(icon, text)
local color, altcolor = parse_color_token(args, colors)
return text, color, altcolor
end
local admin_registered_oninputlinechanged
local function admin_onbeginedit()
-- Optimization to minimize processing while typing.
_cached_state.has_admin_module = flexprompt.is_module_in_prompt("admin")
if not admin_registered_oninputlinechanged and _cached_state.has_admin_module then
if clink.oninputlinechanged then
local function oninputlinechanged(line)
if _cached_state.has_admin_module then
local is_admin
for _,entry in ipairs(clink.parseline(line)) do
local line_state = entry.line_state
local cwi = line_state:getcommandwordindex()
local cw = cwi and line_state:getword(cwi)
if cw == "sudo" or cw == "gsudo" then
is_admin = true
break
end
end
if is_admin ~= _cached_state.is_admin then
_cached_state.is_admin = is_admin
clink.refilterprompt()
end
end
end
admin_registered_oninputlinechanged = true
clink.oninputlinechanged(oninputlinechanged)
end
end
end
--------------------------------------------------------------------------------
-- ANYCONNECT MODULE: {anyconnect:novars:forcetext:text=conn,noconn,unknown:color_options}
--
-- Shows whether Cisco AnyConnect VPN is currently connected as well as the
-- relationship to the HTTP_PROXY and HTTPS_PROXY environment variables.
--
-- The module shows a VPN connected or disconnected icon. The color is chosen
-- depending on the connection status and proxy environment variables (see below
-- for details).
--
-- - 'novars' omits checking the environment variables.
-- - 'forcetext' forces show connection status text even when icons are
-- enabled. The 'text=' argument can override what text is used.
-- - 'text=conn,noconn,unknown' overrides the text for the connection status,
-- which is shown when icons are disabled.
-- - conn = Text to show when connected (default "Connected").
-- - noconn = Text to show when disconnected (default "Disconnected").
-- - unknown = Text to show when unknown or error (default "AnyConnect").
-- - color_options override status colors as follows:
-- - connected=color_name,alt_color_name When connected and env vars are set.
-- - disconnected=color_name,alt_color_name When disconnected and env vars are not set.
-- - partial=color_name,alt_color_name When one env var is set, and one env var is not.
-- - mismatch=color_name,alt_color_name When connected but env vars are not set, or disconnected but env vars are set.
-- - unknown=color_name,alt_color_name When connection status is unknown yet.
local anyconnect_cached_info = {}
local anyconnect_colors =
{
connected = { "c", "connected", fg="94", bg="104", extfg="38;5;12", extbg="48;5;12", },
disconnected = { "d", "disconnected", fg="92", bg="102", extfg="38;5;2", extbg="48;5;2", },
mismatch = { "m", "mismatch", fg="91", bg="101", extfg="38;5;9", extbg="48;5;9", },
partial = { "p", "partial", fg="93", bg="103", extfg="38;5;11", extbg="48;5;11", },
unknown = { "u", "unknown", fg="37", bg="47", extfg="38;5;7", extbg="48;5;7", },
}
local function parse_inline_color(args, colors)
local parsed_colors = flexprompt.parse_arg_token(args, colors[1], colors[2])
local color = flexprompt.use_best_color(colors.fg, colors.extfg or colors.fg)
local altcolor = flexprompt.use_best_color(colors.bg, colors.extbg or colors.bg)
return flexprompt.parse_colors(parsed_colors, color, altcolor)
end
-- Collects connection info.
--
-- Uses async coroutine calls.
local function collect_anyconnect_info()
-- We may want to let the user provide a command to run
-- but then how do we parse the output ?
-- they could give us the pattern to search for as well
local file, pclose = flexprompt.popenyield("vpncli state 2>nul")
if not file then return end
local conns = {}
for line in file:lines() do
-- Strip the lines of any whitespaces
line = line:match( "^%s*(.-)%s*$" )
-- If we have something left add it
if line ~= "" and #line > 0 then
table.insert(conns, line)
end
end
local ok, msg, code -- luacheck: no unused
if type(pclose) == "function" then
ok, msg, code = pclose()
ok = ok and #conns > 0
else
file:close()
ok = #conns > 0
end
if not ok then
return { failed=true, finished=true }
end
-- Check all entries for a given string. It's better to search for
-- Disconnected, since connected is present in both.
--
-- TODO: Apparently Cisco AnyConnect vpncli.exe doesn't have a way to show
-- what you are connected to??
local connected = false
for _,candidate in ipairs(conns) do
-- VPN messages we care about have state in the string, e.g.:
-- >> state: Disconnected
-- >> state: Disconnected
-- >> state: Disconnected
-- >> notice: Ready to connect.
if candidate and #candidate > 0 and candidate:find("state") and
not candidate:find("Disconnected") and not candidate:find("Unknown") then
-- If at least one "state" line doesn't say "Disconnected" or
-- "Unknown", then consider it to be connected.
connected = true
end
end
local tmp = os.getenv("HTTP_PROXY")
local proxy = tmp and #tmp > 0
tmp = os.getenv("HTTPS_PROXY")
local proxys = tmp and #tmp > 0
-- Save connection status as well as information about the HTTP_PROXY and
-- HTTPS_PROXY env variables (if defined and filled in or not).
return { connection=connected, proxy=proxy, proxys=proxys, finished=true }
end
local function render_anyconnect(args)
local info
local refreshing
local wizard = flexprompt.get_wizard_state()
if wizard then
info = { connection=false, proxy=true, proxys=false, finished=true }
else
-- Get connection status.
info, refreshing = flexprompt.prompt_info(anyconnect_cached_info, nil, nil, collect_anyconnect_info)
end
-- Decide on the colors based on the VPN connection state and proxy env vars
-- One bad state env variable results in yellow, both result in red
-- Green for no vpn and no proxy defined, blue for vpn and both proxies defined
local color, altcolor
local novars = flexprompt.parse_arg_keyword(args, "n", "novars")
if not info.finished or info.failed then
color, altcolor = parse_inline_color(args, anyconnect_colors.unknown)
elseif info.connection then
if novars or (info.proxy and info.proxys) then
-- Connected and both vars = CONNECTED (blue).
color, altcolor = parse_inline_color(args, anyconnect_colors.connected)
elseif not info.proxy and not info.proxys then
-- Connected and neither vars = MISMATCHED (red).
color, altcolor = parse_inline_color(args, anyconnect_colors.mismatch)
else
-- Connected and one var = PARTIAL (yellow).
color, altcolor = parse_inline_color(args, anyconnect_colors.partial)
end
else
if novars or (not info.proxy and not info.proxys) then
-- Disconnected and neither vars = DISCONNECTED (green).
color, altcolor = parse_inline_color(args, anyconnect_colors.disconnected)
elseif info.proxy and info.proxys then
-- Disconnected and both vars = MISMATCH (red).
color, altcolor = parse_inline_color(args, anyconnect_colors.mismatch)
else
-- Disconnected and one var = PARTIAL (yellow).
color, altcolor = parse_inline_color(args, anyconnect_colors.partial)
end
end
local icon = refreshing and flexprompt.get_icon("refresh") or nil
if not icon then
icon = flexprompt.get_icon(info.connection and "vpn" or "no_vpn")
if icon == "" then
icon = nil
end
end
local text
if not icon or flexprompt.parse_arg_keyword(args, "f", "forcetext") then
local strings = string.explode(flexprompt.parse_arg_token(args, "t", "text") or "", ",;")
if not info.finished or info.failed then
text = strings[3] or "AnyConnect"
elseif info.connection then
text = strings[1] or "Connected"
else
text = strings[2] or "Disconnected"
end
end
text = flexprompt.append_text(icon, text)
return text, color, altcolor
end
--------------------------------------------------------------------------------
-- BATTERY MODULE: {battery:show=show_level:breakleft:breakright:levelicon:onlyicon}
-- - show_level shows the battery module unless the battery level is greater
-- than show_level.
-- - 'breakleft' adds an empty segment to left of battery in rainbow style.
-- - 'breakright' adds an empty segment to right of battery in rainbow style.
-- - 'levelicon' shows the battery level inside the icon.
-- - 'onlyicon' shows only an icon.
--
-- The 'breakleft' and 'breakright' options may look better than having battery
-- segment colors adjacent to other similarly colored segments in rainbow style.
local rainbow_battery_colors =
{
{
fg = "38;2;239;65;54",
bg = "48;2;239;65;54"
},
{
fg = "38;2;252;176;64",
bg = "48;2;252;176;64"
},
{
fg = "38;2;248;237;50",
bg = "48;2;248;237;50"
},
{
fg = "38;2;142;198;64",
bg = "48;2;142;198;64"
},
{
fg = "38;2;1;148;68",
bg = "48;2;1;148;68"
}
}
local battery_icon_series =
{
[true] = -- Charging.
{
nerdfonts2 = { "","","","","","","" },
nerdfonts3 = { "","","","","","","","","","","" },
},
[false] = -- Not charging.
{
nerdfonts2 = { "","","","","","","","","","","" },
nerdfonts3 = { "","","","","","","","","","","" },
},
}
local function get_battery_status(levelicon, onlyicon)
local level, acpower, charging
local wizard = flexprompt.get_wizard_state()
local status = wizard and wizard.battery or os.getbatterystatus()
level = status.level
acpower = status.acpower
charging = status.charging
if not level or level < 0 then
return "", 0
end
local batt_symbol
if charging then
batt_symbol = flexprompt.get_symbol("charging")
elseif acpower then
batt_symbol = flexprompt.get_symbol("smartcharging")
end
if not batt_symbol or batt_symbol == "" then
batt_symbol = flexprompt.get_symbol("battery")
end
if levelicon then
local series = battery_icon_series[charging]
if series then
series = series[flexprompt.get_nerdfonts_version()]
if series then
batt_symbol = series[math.floor(level * (#series - 1) / 100) + 1]
if flexprompt.get_nerdfonts_width() == 2 then
batt_symbol = batt_symbol .. " "
end
end
end
end
if not onlyicon then
batt_symbol = level..batt_symbol
end
return batt_symbol, level
end
local function get_battery_status_color(level)
if flexprompt.can_use_extended_colors() then
local index = ((((level > 0) and level or 1) - 1) / 20) + 1
index = math.modf(index)
return rainbow_battery_colors[index], index == 1
elseif level > 50 then
return "green"
elseif level > 30 then
return "yellow"
end
return "red", true
end
local prev_battery_status, prev_battery_level
local function update_battery_prompt(levelicon, onlyicon)
while true do
local status,level = get_battery_status(levelicon, onlyicon)
if prev_battery_status ~= status or prev_battery_level ~= level then
clink.refilterprompt()
end
coroutine.yield()
end
end
local function render_battery(args)
if not os.getbatterystatus then return end
local show = tonumber(flexprompt.parse_arg_token(args, "s", "show") or "100")
local onlyicon = flexprompt.parse_arg_keyword(args, "oi", "onlyicon")
local levelicon = flexprompt.parse_arg_keyword(args, "li", "levelicon")
local batteryStatus,level = get_battery_status(levelicon, onlyicon)
prev_battery_status = batteryStatus
prev_battery_level = level
if clink.addcoroutine and flexprompt.settings.battery_idle_refresh ~= false and not _cached_state.battery_coroutine then
local t = coroutine.create(function ()
update_battery_prompt(levelicon, onlyicon)
end)
_cached_state.battery_coroutine = t
clink.addcoroutine(t, flexprompt.settings.battery_refresh_interval or 15)
end
-- Hide when on AC power and fully charged, or when level is less than or
-- equal to the specified 'show=level' ({battery:show=75} means "show at 75
-- or lower").
if not batteryStatus or batteryStatus == "" or level > (show or 80) then
return
end
local style = flexprompt.get_style()
-- The 'breakleft' and 'breakright' args add blank segments to force a color
-- break between rainbow segments, in case adjacent colors are too similar.
local bl, br
if style == "rainbow" then
bl = flexprompt.parse_arg_keyword(args, "bl", "breakleft")
br = flexprompt.parse_arg_keyword(args, "br", "breakright")
end
local color, warning = get_battery_status_color(level)
if warning and style == "classic" then
-- batteryStatus = flexprompt.make_fluent_text(sgr(color.bg .. ";30") .. batteryStatus)
-- The "22;" defeats the color parsing that would normally generate
-- corresponding fg and bg colors even though only an explicit bg color
-- was provided (versus a usual {fg=x,bg=y} color table).
local c = flexprompt.lookup_color(color)
color = "22;" .. c.bg .. ";30"
end
local segments = {}
if bl then table.insert(segments, { "", "realblack" }) end
table.insert(segments, { batteryStatus, color, "realblack" })
if br then table.insert(segments, { "", "realblack" }) end
return segments
end
--------------------------------------------------------------------------------
-- BREAK MODULE: {break:color=color_name}
-- - color_name is a name like "green", or an sgr code like "38;5;60".
--
-- Inserts a break between segments. If there is no visible segment to the left
-- or right of the break, then the break is discarded.
local function render_break(args)
local colors = flexprompt.parse_arg_token(args, "c", "color")
local color = flexprompt.colors.default
color = flexprompt.parse_colors(colors, color)
return { "", color, isbreak=true }
end
--------------------------------------------------------------------------------
-- CONDA MODULE: {conda:color=color_name,alt_color_name}
-- - color_name is a name like "green", or an sgr code like "38;5;60".
-- - alt_color_name is optional; it is the text color in rainbow style.
-- - truncate is optional; if the conda environment is longer than this many
-- directory levels, only the rightmost names are kept (the default is 1,
-- and 0 means don't truncate).
--
-- Shows the current Conda environment, if %CONDA_DEFAULT_ENV% is set.
local function render_conda(args)
local conda = os.getenv("CONDA_DEFAULT_ENV")
if not conda or conda == "" then
return
end
local colors = flexprompt.parse_arg_token(args, "c", "color")
local color, altcolor
local style = flexprompt.get_style()
if style == "rainbow" then
color = flexprompt.use_best_color("green", "38;5;40")
altcolor = "realblack"
elseif style == "classic" then
color = flexprompt.use_best_color("green", "38;5;40")
else
color = flexprompt.use_best_color("green", "38;5;40")
end
color, altcolor = flexprompt.parse_colors(colors, color, altcolor) -- luacheck: ignore 321
local text = ""
local truncate = flexprompt.parse_arg_token(args, "t", "truncate") or 1
truncate = tonumber(truncate)
if truncate > 0 then
while truncate > 0 do
truncate = truncate - 1
local last = conda:match("([/\\][^/\\]+)$") or conda
text = last .. text
conda = conda:sub(1, #conda - #last)
end
text = text:gsub("^[/\\]+", "")
else
text = conda
end
text = "(" .. text .. ")"
text = flexprompt.append_text(flexprompt.get_module_symbol(), text)
return text, color, altcolor
end
--------------------------------------------------------------------------------
-- CWD MODULE: {cwd:color=color_name,alt_color_name:rootcolor=rootcolor_name:type=type_name:shorten}
-- - color_name is a name like "green", or an sgr code like "38;5;60".
-- - alt_color_name is optional; it is the text color in rainbow style.
-- - rootcolor_name overrides the repo parent color when using "rootsmart".
-- - type_name is the format to use:
-- - "full" is the full path.
-- - "folder" is just the folder name.
-- - "smart" is the git repo\subdir, or the full path.
-- - "rootsmart" is the full path, with parent of git repo not colored.
--
-- The 'shorten' option can abbreviate parent directories to the shortest string
-- that uniquely identifies the directory. The first and last directories in
-- the string are never abbreviated, and git repo or workspace directories are
-- never abbreviated. Abbreviation occurs when the cwd string is longer than 80
-- columns (2 line style) or longer than half the terminal width (1 line style),
-- or when the terminal width is not wide enough for the left and right prompts
-- to fit on a single line.
-- The 'shorten' option may optionally be followed by "=rootsmart" to abbreviate
-- only the repo's parent directories when in a git repo (otherwise abbreviate
-- all the parent directories), or by "=32" or other number to only use
-- abbreviation when the path is longer than the specified number of columns.
--
-- The default type is "rootsmart" if not specified.
-- Returns the folder name of the specified directory.
-- - For c:\foo\bar it yields bar
-- - For c:\ it yields c:\
-- - For \\server\share\subdir it yields subdir
-- - For \\server\share it yields \\server\share
local function get_folder_name(dir)
local parent, child = path.toparent(dir)
return child == "" and parent or child
end
local function abbreviate_parents(dir, all)
return flexprompt.abbrev_path(dir, true, all)
end
local function process_cwd_string(cwd, git_wks, args)
local shorten = flexprompt.parse_arg_keyword(args, "s", "shorten") and "all"
if not shorten then
shorten = flexprompt.parse_arg_token(args, "s", "shorten")
local threshold = tonumber(shorten)
if threshold and threshold > 0 then
shorten = threshold
end
end
if not shorten then
if flexprompt.get_lines() == 1 then
shorten = console.getwidth() / 2
else
shorten = 80
end
end
local real_git_dir -- luacheck: no unused
local sym
local ptype = flexprompt.parse_arg_token(args, "t", "type") or "rootsmart"
if ptype == "folder" then
return get_folder_name(cwd)
end
local tilde -- luacheck: no unused
local orig_cwd = cwd
cwd, tilde = flexprompt.maybe_apply_tilde(cwd)
if ptype == "smart" or ptype == "rootsmart" then
if git_wks == nil then -- Don't double-hunt for it!
real_git_dir, git_wks = flexprompt.get_git_dir(orig_cwd)
end
if git_wks then
-- Get the git workspace folder name and reappend any part
-- of the directory that comes after.
-- Ex: C:\Users\username\some-repo\innerdir -> some-repo\innerdir
git_wks = flexprompt.maybe_apply_tilde(git_wks)
local git_wks_parent = path.toparent(git_wks) -- Don't use get_parent() here!
local appended_dir = string.sub(cwd, string.len(git_wks_parent) + 1)
local smart_dir = get_folder_name(git_wks_parent) .. appended_dir
if ptype == "rootsmart" then
local rootcolor = flexprompt.parse_arg_token(args, "rc", "rootcolor")
local parent = cwd:sub(1, #cwd - #smart_dir)
if shorten and (type(shorten) ~= "number" or console.cellcount(cwd) > shorten) then
parent = abbreviate_parents(parent, true--[[all]])
if shorten ~= "smartroot" and shorten ~= "rootsmart" then
smart_dir = abbreviate_parents(smart_dir)
end
shorten = nil
end
cwd = flexprompt.make_fluent_text(parent, rootcolor or true) .. smart_dir
else
cwd = smart_dir
end
local tmp = flexprompt.get_icon("cwd_git_symbol")
sym = (tmp ~= "") and tmp or nil
end
end
if shorten and (type(shorten) ~= "number" or console.cellcount(cwd) > shorten) then
cwd = abbreviate_parents(cwd)
shorten = nil
end
return cwd, sym, not shorten
end
local function render_cwd(args)
local colors = flexprompt.parse_arg_token(args, "c", "color")
local color, altcolor
local style = flexprompt.get_style()
if style == "rainbow" then
color = flexprompt.use_best_color("blue", "38;5;19")
elseif style == "classic" then
color = flexprompt.use_best_color("cyan", "38;5;39")
else
color = flexprompt.use_best_color("blue", "38;5;33")
end
color, altcolor = flexprompt.parse_colors(colors, color, altcolor) -- luacheck: ignore 321
local wizard = flexprompt.get_wizard_state()
local cwd = wizard and wizard.cwd or os.getcwd()
local git_wks = wizard and (wizard.git_dir or false)
local text, sym, shortened = process_cwd_string(cwd, git_wks, args)
sym = sym or flexprompt.get_module_symbol()
text = flexprompt.append_text(flexprompt.get_dir_stack_depth(), text)
text = flexprompt.append_text(sym, text)
local results = {
text=text,
color=color,
altcolor=altcolor,
}
if not shortened then
results.condense_callback = function ()
return {
text=flexprompt.append_text(sym, flexprompt.abbrev_path(cwd, true)),
color=color,
altcolor=altcolor,
}
end
end
return results
end
--------------------------------------------------------------------------------
-- DURATION MODULE: {duration:format=format_name:tenths:color=color_name,alt_color_name}
-- - format_name is the format to use:
-- - "colons" is "H:M:S" format.
-- - "letters" is "Hh Mm Ss" format (the default).
-- - tenths includes tenths of seconds.
-- - color_name is a name like "green", or an sgr code like "38;5;60".
-- - alt_color_name is optional; it is the text color in rainbow style.
--
-- Use the "luafunc:flexprompt_toggle_tenths" command to toggle displaying
-- tenths of seconds. By default it is bound to Ctrl+Alt+T.
if rl.setbinding then
if not rl.getbinding([["\e\C-T"]]) then
rl.setbinding([["\e\C-T"]], [["luafunc:flexprompt_toggle_tenths"]])
end
if rl.describemacro then
rl.describemacro([["luafunc:flexprompt_toggle_tenths"]], "Toggle displaying tenths of seconds for duration in the prompt")
end
end
function flexprompt_toggle_tenths(rl_buffer) -- luacheck: no global, no unused
local modules = flexprompt.get_duration_modules()
if modules then
flexprompt.settings.force_duration = true
flexprompt.settings.duration_invert_tenths = not flexprompt.settings.duration_invert_tenths
for module in pairs(modules) do
flexprompt.refilter_module(module)
end
clink.refilterprompt()
end
end
local function render_duration(args)
local wizard = flexprompt.get_wizard_state()
local duration = flexprompt.get_duration()
if not flexprompt.settings.force_duration and duration < (flexprompt.settings.duration_threshold or 3) then return end
local colors = flexprompt.parse_arg_token(args, "c", "color")
local color, altcolor
if flexprompt.get_style() == "rainbow" then
color = flexprompt.use_best_color("yellow", "38;5;136")
altcolor = "realblack"
else
color = flexprompt.use_best_color("darkyellow", "38;5;214")
end
color, altcolor = flexprompt.parse_colors(colors, color, altcolor)
local h, m, s
local t = math.floor(duration * 10) % 10
duration = math.floor(duration)
s = (duration % 60)
duration = math.floor(duration / 60)
if duration > 0 then
m = (duration % 60)
duration = math.floor(duration / 60)
if duration > 0 then
h = duration
end
end
local tenths = flexprompt.parse_arg_keyword(args, "t", "tenths")
if wizard then
tenths = wizard.duration_tenths
elseif flexprompt.settings.invert_tenths then
tenths = not tenths
end
local text
local format = flexprompt.parse_arg_token(args, "f", "format")
if format and format == "colons" then
if h then
text = string.format("%u:%02u:%02u", h, m, s)
else
text = string.format("%u:%02u", (m or 0), s)
end
if tenths then
text = text .. "." .. t
end
else
if tenths then
s = s .. "." .. t
end
text = s .. "s"
if m then
text = flexprompt.append_text(m .. "m", text)
if h then
text = flexprompt.append_text(h .. "h", text)
end
end
end
if flexprompt.get_flow() == "fluent" then
text = flexprompt.append_text(flexprompt.make_fluent_text("took"), text)
end
text = flexprompt.append_text(text, flexprompt.get_module_symbol())
return text, color, altcolor
end
--------------------------------------------------------------------------------
-- ENV MODULE: {env:var=var_name,color=color_name,alt_color_name:label=label_text:fluent=fluent_text}
-- - var_name can be any environment variable name.
-- - color_name is a name like "green", or an sgr code like "38;5;60".
-- - alt_color_name is optional; it is the text color in rainbow style.
-- - label_text is optional text to use as a label prefix before the
-- environment variable's value.
-- - fluent_text is optional text to use as a prefix when "fluent" mode is
-- enabled.
local function render_env(args)
local name = flexprompt.parse_arg_token(args, "v", "var") or ""
if name == "" then
return
end
local text = os.getenv(name) or ""
if text == "" then
return
end
local label = flexprompt.parse_arg_token(args, "l", "label") or ""
if label ~= "" then
text = flexprompt.append_text(label, text)
end
local fluent = flexprompt.parse_arg_token(args, "f", "fluent") or ""
if fluent ~= "" then
text = flexprompt.append_text(flexprompt.make_fluent_text(fluent), text)
end
local colors = flexprompt.parse_arg_token(args, "c", "color")
local color, altcolor
local style = flexprompt.get_style()
if style == "rainbow" then
color = flexprompt.use_best_color("green", "38;5;22")
elseif style == "classic" then
color = flexprompt.use_best_color("green", "38;5;35")
else
color = flexprompt.use_best_color("green", "38;5;28")
end
color, altcolor = flexprompt.parse_colors(colors, color, altcolor) -- luacheck: ignore 321
return text, color, altcolor
end
--------------------------------------------------------------------------------
-- EXIT MODULE: {exit:always:color=color_name,alt_color_name:hex}
-- - 'always' always shows the exit code even when 0.
-- - color_name is used when the exit code is 0, and is a name like "green", or
-- an sgr code like "38;5;60".
-- - alt_color_name is optional; it is the text color in rainbow style when the
-- exit code is 0.
-- - 'hex' forces hex display for values > 255 or < -255. Otherwise hex
-- display is used for values > 32767 or < -32767.
local function render_exit(args)
if not os.geterrorlevel then return end
local text
local value = flexprompt.get_errorlevel()
local always = flexprompt.parse_arg_keyword(args, "a", "always")
if not always and value == 0 then return end
local hex = flexprompt.parse_arg_keyword(args, "h", "hex")
if math.abs(value) > (hex and 255 or 32767) then
local lo = bit32.band(value, 0xffff)
local hi = bit32.rshift(value, 16)
if hi > 0 then
hex = string.format("%x", hi) .. string.format("%04.4x", lo)
else
hex = string.format("%x", lo)
end
text = "0x" .. hex
else
text = value
end
local colors = flexprompt.parse_arg_token(args, "c", "color")
local color, altcolor
color = value ~= 0 and "exit_nonzero" or "exit_zero"
color, altcolor = flexprompt.parse_colors(colors, color, altcolor) -- luacheck: ignore 321
local sym = flexprompt.get_module_symbol()
if sym == "" then
sym = flexprompt.get_icon(value ~= 0 and "exit_nonzero" or "exit_zero")
end
text = flexprompt.append_text(sym, text)
if flexprompt.get_flow() == "fluent" then
text = flexprompt.append_text(flexprompt.make_fluent_text("exit"), text)
end
return text, color, altcolor
end
--------------------------------------------------------------------------------
-- GIT MODULE: {git:nostaged:noaheadbehind:counts:color_options}
-- - 'nountracked' omits untracked files.
-- - 'nostaged' omits the staged details.
-- - 'noaheadbehind' omits the ahead/behind details.
-- - 'showremote' shows the branch and its remote.
-- - 'submodules' includes status for submodules.
-- - 'counts' shows the count of added/modified/etc files.
-- - color_options override status colors as follows:
-- - clean=color_name,alt_color_name When status is clean.
-- - conflict=color_name,alt_color_name When a conflict exists.
-- - dirty=color_name,alt_color_name When status is dirty.
-- - remote=color_name,alt_color_name For ahead/behind details.
-- - staged=color_name,alt_color_name For staged details.
-- - unknown=color_name,alt_color_name When status is unknown.
-- - unpublished=color_name,alt_color_name When status is clean but branch is not published.
-- luacheck: globals flexprompt_git
flexprompt_git = flexprompt_git or {}
-- .postprocess_branch = function(string), returns string, can modify the branch name string.
local git = {}
local fetched_repos = {}
-- Add status details to the segment text.
--
-- Synchronous call.
local function add_details(text, details, include_counts)
local add = details.add or 0
local modify = details.modify or 0
local delete = details.delete or 0
local rename = details.rename or 0
local untracked = details.untracked or 0
if include_counts then
if add > 0 then
text = flexprompt.append_text(text, flexprompt.get_symbol("addcount") .. add)
end
if modify > 0 then
text = flexprompt.append_text(text, flexprompt.get_symbol("modifycount") .. modify)
end
if delete > 0 then
text = flexprompt.append_text(text, flexprompt.get_symbol("deletecount") .. delete)
end
if rename > 0 then
text = flexprompt.append_text(text, flexprompt.get_symbol("renamecount") .. rename)
end
elseif (add + modify + delete + rename) > 0 then
text = flexprompt.append_text(text, flexprompt.get_symbol("summarycount") .. (add + modify + delete + rename))
end
if untracked > 0 then
text = flexprompt.append_text(text, flexprompt.get_symbol("untrackedcount") .. untracked)
end
return text
end
local function maybe_git_fetch(info)
if info.type == "git" and flexprompt.settings.git_fetch_interval then
local when = fetched_repos[info.root]
if not when or os.clock() - when > flexprompt.settings.git_fetch_interval * 60 then
local file = flexprompt.popenyield(flexprompt.git_command("fetch"))
if file then
file:close()
end
fetched_repos[info.root] = os.clock()
end
end
end
-- Collects git status info.
--
-- Uses async coroutine calls.
local function collect_git_info(no_untracked, includeSubmodules)
local git_dir, wks_dir = flexprompt.get_git_dir()
git_dir = git_dir and git_dir:lower()
wks_dir = wks_dir and wks_dir:lower()
local submodule = git_dir and git_dir:find(path.join(wks_dir, "modules\\"), 1, true) == 1
maybe_git_fetch({ type="git", root=git_dir })
local status = flexprompt.get_git_status(no_untracked, includeSubmodules)
local conflict = flexprompt.get_git_conflict()
local ahead, behind = flexprompt.get_git_ahead_behind()