PowerLine.cs

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
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
using System.Text;
using System.Text.RegularExpressions;
 
namespace PowerLine
{
    public static class AnsiHelper
    {
        public static string GetCode(this ConsoleColor? color, bool forBackground = false)
        {
            string colorCode = color == null ? "Clear" : color.ToString();
 
            return forBackground ? Background[colorCode] : Foreground[colorCode];
 
        }
        public static Dictionary<string, string> Foreground = new Dictionary<string, string>
            {
                {"Clear", "\u001B[39m"},
                {"Black", "\u001B[30m"}, { "DarkGray", "\u001B[90m"},
                {"DarkRed", "\u001B[31m"}, { "Red", "\u001B[91m"},
                {"DarkGreen", "\u001B[32m"}, { "Green", "\u001B[92m"},
                {"DarkYellow", "\u001B[33m"}, { "Yellow", "\u001B[93m"},
                {"DarkBlue", "\u001B[34m"}, { "Blue", "\u001B[94m"},
                {"DarkMagenta", "\u001B[35m"}, { "Magenta", "\u001B[95m"},
                {"DarkCyan", "\u001B[36m"}, { "Cyan", "\u001B[96m"},
                {"Gray", "\u001B[37m"}, { "White", "\u001B[97m"}
            };
 
        public static Dictionary<string, string> Background = new Dictionary<string, string>
            {
                {"Clear", "\u001B[49m"},
                {"Black", "\u001B[40m"}, {"DarkGray", "\u001B[100m"},
                {"DarkRed", "\u001B[41m"}, {"Red", "\u001B[101m"},
                {"DarkGreen", "\u001B[42m"}, {"Green", "\u001B[102m"},
                {"DarkYellow", "\u001B[43m"}, {"Yellow", "\u001B[103m"},
                {"DarkBlue", "\u001B[44m"}, {"Blue", "\u001B[104m"},
                {"DarkMagenta", "\u001B[45m"}, {"Magenta", "\u001B[105m"},
                {"DarkCyan", "\u001B[46m"}, {"Cyan", "\u001B[106m"},
                {"Gray", "\u001B[47m"}, {"White", "\u001B[107m"},
            };
 
 
        public static string WriteAnsi(ConsoleColor? foreground, ConsoleColor? background, string value, bool clear = false)
        {
            var output = new StringBuilder();
 
            output.Append(background.GetCode(true));
            output.Append(foreground.GetCode());
 
            output.Append(value);
            if (clear)
            {
                output.Append(AnsiHelper.Background["Clear"]);
                output.Append(AnsiHelper.Foreground["Clear"]);
            }
            return output.ToString();
        }
 
        public static string GetString(object @object)
        {
            return (string)LanguagePrimitives.ConvertTo(@object is ScriptBlock ? ((ScriptBlock)@object).Invoke() : @object, typeof(string));
        }
 
        public struct EscapeCodes
        {
            public static readonly string ESC = "\u001B[";
            public static readonly string Clear = "\u001B[0m";
            public static readonly string Store = "\u001B[s";
            public static readonly string Recall = "\u001B[u";
        };
    }
 
    public class Block
    {
        public static string LeftCap = "\ue0b0"; // right-pointing arrow
        public static string RightCap = "\ue0b2"; // left-pointing arrow
        public static string LeftSep = "\ue0b1"; // left open >
        public static string RightSep = "\ue0b3"; // right open <
        public static string Branch = "\ue0a0"; // Branch symbol
        public static string LOCK = "\ue0a2"; // Padlock
        public static string GEAR = "\u26ef"; // The settings icon, I use it for debug
        public static string POWER = "\u26a1"; // The Power lightning-bolt icon
 
        public ConsoleColor? BackgroundColor { get; set; }
        public ConsoleColor? ForegroundColor { get; set; }
 
        public object Object { get; set; }
 
        public bool Clear { get; set; }
 
        public Block() { }
 
        // copy constructor
        public Block(Block block)
        {
            BackgroundColor = block.BackgroundColor;
            ForegroundColor = block.ForegroundColor;
            Object = block.Object;
        }
 
        public Block(IDictionary values)
        {
            foreach (string key in values.Keys)
            {
                var pattern = "^" + Regex.Escape(key);
                if ("bg".Equals(key, System.StringComparison.InvariantCultureIgnoreCase) || Regex.IsMatch("BackgroundColor", pattern, RegexOptions.IgnoreCase))
                {
                    BackgroundColor = (ConsoleColor)Enum.Parse(typeof(ConsoleColor), values[key].ToString(), true);
                }
                else if ("fg".Equals(key, System.StringComparison.InvariantCultureIgnoreCase) || Regex.IsMatch("ForegroundColor", pattern, RegexOptions.IgnoreCase))
                {
                    ForegroundColor = (ConsoleColor)Enum.Parse(typeof(ConsoleColor), values[key].ToString(), true);
                }
                else if (Regex.IsMatch("text", pattern, RegexOptions.IgnoreCase) || Regex.IsMatch("Content", pattern, RegexOptions.IgnoreCase) || Regex.IsMatch("Object", pattern, RegexOptions.IgnoreCase))
                {
                    Object = values[key];
                }
                else if (Regex.IsMatch("Clear", pattern, RegexOptions.IgnoreCase))
                {
                    Clear = (bool)values[key];
                }
                else
                {
                    throw new ArgumentException("Unknown key '" + key + "' in hashtable. Allowed values are BackgroundColor, ForegroundColor, Content, and Clear");
                }
            }
        }
 
        /// <summary>
        /// Get the Object rendered to text.
        /// With special handling for ScriptBlocks and Blocks, and ScriptBlocks which output Blocks...
        /// </summary>
        /// <returns></returns>
        public string GetObjectText()
        {
            object value = Object;
 
            if (Object is ScriptBlock)
            {
                value = ((ScriptBlock)Object).Invoke();
            }
 
            if (Object is IEnumerable<ScriptBlock>)
            {
                value = ((IEnumerable<ScriptBlock>)Object)
                            .SelectMany(block => block.Invoke()
                                .Select(pso => pso.BaseObject))
                            .Select(o => o is Block ? ((Block)o).GetObjectText() : o);
            }
 
            if (value is Block)
            {
                value = ((Block)value).GetObjectText();
            }
 
            if (value is IEnumerable<Block>)
            {
                value = ((IEnumerable<Block>)value).Select(block => block.GetObjectText());
            }
 
            if (value is IEnumerable<object>)
            {
                value = ((IEnumerable<object>)value).Select(o => o is Block ? ((Block)o).GetObjectText() : o);
            }
 
            return (string)LanguagePrimitives.ConvertTo(value, typeof(string));
        }
 
        public override string ToString()
        {
            object value = Object;
            string text = null;
 
            if (Object is ScriptBlock)
            {
                value = ((ScriptBlock)Object).Invoke();
            }
            if (value is Block)
            {
                return value.ToString();
            }
 
            if (Object is IEnumerable<ScriptBlock>)
            {
                text = AnsiHelper.GetString(((IEnumerable<ScriptBlock>)Object)
                            .SelectMany(block => block.Invoke()
                                .Select(pso => pso.BaseObject))
                            .Select(o => o is Block ? ((Block)o).ToString() : AnsiHelper.GetString(o)));
            }
 
            if (value is IEnumerable<Block>)
            {
                text = AnsiHelper.GetString(((IEnumerable<Block>)value).Select(block => block.ToString()));
            }
 
            if (value is IEnumerable<object>)
            {
                text = AnsiHelper.GetString(((IEnumerable<object>)value).Select(o => o is Block ? ((Block)o).ToString() : AnsiHelper.GetString(o)));
            }
 
            return AnsiHelper.WriteAnsi(ForegroundColor, BackgroundColor, text ?? GetObjectText(), Clear);
        }
 
        // override object.Equals
        public override bool Equals(object obj)
        {
            if (obj == null || GetType() != obj.GetType())
            {
                // Console.WriteLine(GetType().FullName + " is not " + obj.GetType().FullName);
                return false;
            }
 
            var other = obj as Block;
 
            // Console.WriteLine("Is " + GetType().FullName + " '" + Content + "' == " + obj.GetType().FullName + " '" + other.Content + "'");
            return Object == other.Object && ForegroundColor == other.ForegroundColor && BackgroundColor == other.BackgroundColor;
        }
 
        // override object.GetHashCode
        public override int GetHashCode()
        {
            return Object.GetHashCode() + BackgroundColor.GetHashCode() + ForegroundColor.GetHashCode();
        }
    }
 
    public class BlockCache : Block
    {
        private string _text;
        new public string Object
        {
            get
            {
                return _text ?? GetObjectText();
            }
            set
            {
                _text = value;
                base.Object = value;
                Length = _text.Length;
            }
        }
 
        public int Length { get; private set; }
 
        public static BlockCache Column = new BlockCache() { Object = "\t" };
        public static BlockCache Prompt = new BlockCache() { Object = AnsiHelper.EscapeCodes.Store };
 
        public BlockCache() { }
 
        public BlockCache(Block block)
        {
            BackgroundColor = block.BackgroundColor;
            ForegroundColor = block.ForegroundColor;
            Object = block.GetObjectText();
            Length = Object.Length;
        }
 
        public override string ToString()
        {
            return AnsiHelper.WriteAnsi(ForegroundColor, BackgroundColor, Object, Clear);
        }
    }
 
    public static class Cacher
    {
        public static BlockCache Cache(this Block block)
        {
            return block is BlockCache ? (BlockCache)block : new BlockCache(block);
        }
    }
 
    public class Line : List<Block>
    {
        public Line() { }
 
        public Line(params Block[] blocks) : base(blocks) { }
 
        public Line(object[] blocks)
        {
            AddRange(blocks.Select(b => b is Hashtable ? new Block((Hashtable)b) : (Block)b));
        }
 
        public override string ToString()
        {
            // Initialize variables ...
            var width = Console.BufferWidth;
            var leftLength = 0;
            var rightLength = 0;
 
            // Precalculate all the text and remove empty blocks
            var ValidBlocks = this.Select(e => e.Cache()).Where(e => e.Length > 0).ToArray();
 
            var output = new StringBuilder();
            // Output each block with appropriate separators and caps
            for (int l = 0; l < ValidBlocks.Length; l++)
            {
                var block = ValidBlocks[l];
 
                // Console.WriteLine("Is '" + block + "' a Column? " + BlockCache.Column.Equals(block) + " or a Prompt? " + BlockCache.Prompt.Equals(block));
                if (BlockCache.Column.Equals(block))
                {
                    // the length of the second column
                    rightLength = ValidBlocks.Skip(l + 1).Sum(e => e.Length + 1) - 1;
 
                    var space = width - rightLength;
 
                    // Output a cap on the left if there isn't one already
                    if (l > 0 && !BlockCache.Prompt.Equals(ValidBlocks[l - 1]))
                    {
                        // Use the Background of the previous block as the foreground
                        output.Append(AnsiHelper.WriteAnsi(ValidBlocks[l - 1].BackgroundColor, null, Block.LeftCap, true));
                    }
 
                    output.Append(AnsiHelper.EscapeCodes.ESC + space + "G");
 
                    if (l < ValidBlocks.Length)
                    {
                        // the right cap uses the background of the next block as it's foreground
                        output.Append(AnsiHelper.WriteAnsi(ValidBlocks[l + 1].BackgroundColor, null, Block.RightCap));
                    }
                }
                else if (BlockCache.Prompt.Equals(block))
                {
                    output.Append(block.ToString());
                }
                else
                {
                    if (leftLength == 0 && rightLength == 0)
                    {
                        // On a new line, recalculate the length of the "left-aligned" line
                        leftLength = ValidBlocks.TakeWhile(e => !BlockCache.Column.Equals(e)).Sum(e => e.Length + 1);
                    }
 
                    output.Append(block.ToString());
 
                    // Write a separator between blocks
                    if (l + 1 < ValidBlocks.Length && !BlockCache.Column.Equals(ValidBlocks[l + 1]))
                    {
                        // if the next block is the sambe background color, use a >
                        if (block.BackgroundColor == ValidBlocks[l + 1].BackgroundColor)
                        {
                            output.Append(rightLength > 0 ? Block.RightSep : Block.LeftSep);
                        }
                        else
                        {
                            if (rightLength > 0)
                            {
                                output.Append(AnsiHelper.WriteAnsi(ValidBlocks[l + 1].BackgroundColor, block.BackgroundColor, Block.RightCap));
                            }
                            else
                            {
                                output.Append(AnsiHelper.WriteAnsi(block.BackgroundColor, ValidBlocks[l + 1].BackgroundColor, Block.LeftCap));
                            }
                        }
                    }
                }
            }
 
            // Output a cap on the left if we didn't already
            if (rightLength == 0 && leftLength > 0)
            {
                output.Append(AnsiHelper.WriteAnsi(ValidBlocks.Last().BackgroundColor, null, Block.LeftCap));
            }
            return output.ToString();
        }
    }
 
    public class Prompt : List<Line>
    {
        public bool SetTitle { get; set; }
        public bool SetCurrentDirectory { get; set; }
        public int PrefixLines { get; set; }
 
        public Prompt() { }
 
        public Prompt(int prefixLines, params Line[] lines) : base(lines)
        {
            PrefixLines = prefixLines;
        }
 
        public Prompt(params Line[] lines) : base(lines) { }
 
        public Prompt(object[] lines)
        {
            if (lines.First() is int)
            {
                PrefixLines = (int)lines.First();
                lines = lines.Skip(1).ToArray();
            }
 
            AddRange(lines.Select(b => b is Block[] ?
                                        new Line((Block[])b) :
                                            b is object[] ?
                                                new Line((object[])b) :
                                                (Line)b));
        }
 
        public override string ToString()
        {
            var output = new StringBuilder();
 
            // Move up to previous line(s)
            if (PrefixLines != 0)
            {
                output.Append(AnsiHelper.EscapeCodes.ESC + Math.Abs(PrefixLines) + "A");
            }
            output.Append(string.Join("\n", this));
 
            if (this.Any(line => line.Any(block => BlockCache.Prompt.Equals(block))))
            {
                output.Append(AnsiHelper.EscapeCodes.Recall);
            }
            output.Append(AnsiHelper.Foreground["Clear"]); // Default
            output.Append(AnsiHelper.Background["Clear"]); // Default
            return output.ToString();
        }
    }
}