• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

MorganKryze / ConsoleAppVisuals / 8471182871

28 Mar 2024 05:20PM UTC coverage: 96.244%. Remained the same
8471182871

push

github

MorganKryze
🚑 (CD) fix regex to accept -beta.1 format

946 of 1056 branches covered (89.58%)

Branch coverage included in aggregate %.

2129 of 2139 relevant lines covered (99.53%)

249.89 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.86
/src/ConsoleAppVisuals/elements/interactive_elements/TableSelector.cs
1
/*
2
    Copyright (c) 2024 Yann M. Vidamment (MorganKryze)
3
    Licensed under GNU GPL v2.0. See full license at: https://github.com/MorganKryze/ConsoleAppVisuals/blob/main/LICENSE.md
4
*/
5
namespace ConsoleAppVisuals.InteractiveElements;
6

7
/// <summary>
8
/// The <see cref="TableSelector"/> is an interactive element that displays a table with selectable elements.
9
/// </summary>
10
/// <remarks>
11
/// For more information, consider visiting the documentation available <a href="https://morgankryze.github.io/ConsoleAppVisuals/">here</a>.
12
/// </remarks>
13
public class TableSelector : InteractiveElement<int>
14
{
15
    #region Constants
16
    const int DEFAULT_DIMENSIONS = 0;
17
    const string DEFAULT_FOOTER_TEXT = "New";
18
    const string DEFAULT_TITLE = null;
19
    const string DEFAULT_FOOTER = null;
20
    const bool DEFAULT_EXCLUDE_HEADER = true;
21
    const bool DEFAULT_EXCLUDE_FOOTER = true;
22
    const Placement DEFAULT_PLACEMENT = Placement.TopCenter;
23
    const BordersType DEFAULT_BORDERS_TYPE = BordersType.SingleStraight;
24
    #endregion
25

26
    #region Fields
27
    private string? _title;
28
    private List<string>? _rawHeaders;
29
    private List<List<string>>? _rawLines;
30
    private bool _excludeHeader;
31
    private bool _excludeFooter;
32
    private string? _footerText;
33
    private string[]? _displayArray;
34
    private readonly Borders _borders;
35
    private Placement _placement;
36
    #endregion
37

38
    #region Default Properties
39
    /// <summary>
40
    /// Gets the placement of the table on the console.
41
    /// </summary>
42
    public override Placement Placement => _placement;
7✔
43

44
    /// <summary>
45
    /// Gets the height of the table.
46
    /// </summary>
47
    public override int Height => _displayArray?.Length ?? DEFAULT_DIMENSIONS;
2✔
48

49
    /// <summary>
50
    /// Gets the width of the table.
51
    /// </summary>
52
    public override int Width => _displayArray?.Max(x => x.Length) ?? DEFAULT_DIMENSIONS;
15✔
53
    #endregion
54

55
    #region Properties
56
    /// <summary>
57
    /// Gets the title of the table.
58
    /// </summary>
59
    public string? Title => _title;
5✔
60

61
    /// <summary>
62
    /// Gets if the header is excluded.
63
    /// </summary>
64
    public bool ExcludeHeader => _excludeHeader;
4✔
65

66
    /// <summary>
67
    /// Gets if the footer is excluded.
68
    /// </summary>
69
    public bool ExcludeFooter => _excludeFooter;
4✔
70

71
    /// <summary>
72
    /// Gets the text of the footer.
73
    /// </summary>
74
    public string FooterText => _footerText ?? DEFAULT_FOOTER_TEXT;
4✔
75

76
    /// <summary>
77
    /// Gets the borders manager of the table.
78
    /// </summary>
79
    public Borders Borders => _borders;
3,596✔
80

81
    /// <summary>
82
    /// Gets the type of the borders of the table.
83
    /// </summary>
84
    public BordersType BordersType => _borders.Type;
2✔
85

86
    /// <summary>
87
    /// Gets the headers of the table.
88
    /// </summary>
89
    public List<string>? GetRawHeaders => _rawHeaders;
6✔
90

91
    /// <summary>
92
    /// Gets the lines of the table.
93
    /// </summary>
94
    public List<List<string>>? GetRawLines => _rawLines;
7✔
95

96
    /// <summary>
97
    /// Gets the number of lines in the table.
98
    /// </summary>
99
    public int Count => _rawLines?.Count ?? 0;
4✔
100

101
    /// <summary>
102
    /// Gets the display array of the table.
103
    /// </summary>
104
    public string[]? DisplayArray => _displayArray;
2✔
105
    #endregion
106

107
    #region Constructor
108
    /// <summary>
109
    /// The <see cref="TableSelector"/> is an interactive element that displays a table with selectable elements.
110
    /// </summary>
111
    /// <param name="title">The title of the table.</param>
112
    /// <param name="lines">The lines of the table.</param>
113
    /// <param name="headers">The headers of the table.</param>
114
    /// <param name="excludeHeader">Whether to exclude the header from selectable elements.</param>
115
    /// <param name="excludeFooter">Whether to exclude the footer from selectable elements.</param>
116
    /// <param name="footerText">The text to display in the footer.</param>
117
    /// <param name="placement">The placement of the table.</param>
118
    /// <param name="bordersType">The type of the borders of the table.</param>
119
    /// <exception cref="ArgumentException">Thrown when the number of columns in the table is not consistent with itself or with the headers.</exception>
120
    /// <exception cref="NullReferenceException">Thrown when no body lines were provided.</exception>
121
    /// <remarks>
122
    /// For more information, consider visiting the documentation available <a href="https://morgankryze.github.io/ConsoleAppVisuals/">here</a>.
123
    /// </remarks>
124
    public TableSelector(
71✔
125
        string? title = DEFAULT_TITLE,
71✔
126
        List<string>? headers = null,
71✔
127
        List<List<string>>? lines = null,
71✔
128
        bool excludeHeader = DEFAULT_EXCLUDE_HEADER,
71✔
129
        bool excludeFooter = DEFAULT_EXCLUDE_FOOTER,
71✔
130
        string? footerText = DEFAULT_FOOTER,
71✔
131
        Placement placement = DEFAULT_PLACEMENT,
71✔
132
        BordersType bordersType = DEFAULT_BORDERS_TYPE
71✔
133
    )
71✔
134
    {
135
        _title = title;
71✔
136
        _rawHeaders = headers;
71✔
137
        _rawLines = lines;
71✔
138
        _excludeHeader = excludeHeader;
71✔
139
        _excludeFooter = excludeFooter;
71✔
140
        _footerText = footerText;
71✔
141
        _placement = placement;
71✔
142
        _borders = new Borders(bordersType);
71✔
143
        if (CompatibilityCheck())
71✔
144
        {
145
            BuildTable();
49✔
146
        }
147
    }
69✔
148
    #endregion
149

150
    #region Check Methods
151
    private bool CompatibilityCheck()
152
    {
153
        if (_rawHeaders is null)
84✔
154
        {
155
            return CheckRawLines();
16✔
156
        }
157
        else if (_rawLines is null)
68✔
158
        {
159
            return true;
3✔
160
        }
161
        else
162
        {
163
            return CheckRawHeadersAndLines();
65✔
164
        }
165
    }
166

167
    private bool CheckRawLines()
168
    {
169
        if (_rawLines is null || _rawLines.Count == 0)
16✔
170
        {
171
            return false;
11✔
172
        }
173

174
        for (int i = 0; i < _rawLines.Count; i++)
36✔
175
        {
176
            if (_rawLines[i].Count != _rawLines[0].Count)
14✔
177
            {
178
                throw new ArgumentException(
1✔
179
                    "The number of columns in the table is not consistent."
1✔
180
                );
1✔
181
            }
182
        }
183

184
        return true;
4✔
185
    }
186

187
    private bool CheckRawHeadersAndLines()
188
    {
189
        if (_rawLines is null || _rawLines.Count == 0)
65✔
190
        {
191
            return false;
13✔
192
        }
193

194
        for (int i = 0; i < _rawLines.Count; i++)
410✔
195
        {
196
            if (_rawLines[i].Count != _rawHeaders?.Count)
155!
197
            {
198
                throw new ArgumentException(
2✔
199
                    "The number of columns in the table is not consistent(Headers or Lines)."
2✔
200
                );
2✔
201
            }
202
        }
203

204
        return true;
50✔
205
    }
206
    #endregion
207

208
    #region Update Methods
209
    /// <summary>
210
    /// Updates the placement of the table.
211
    /// </summary>
212
    /// <param name="placement">The placement of the table.</param>
213
    /// <remarks>
214
    /// For more information, consider visiting the documentation available <a href="https://morgankryze.github.io/ConsoleAppVisuals/">here</a>.
215
    /// </remarks>
216
    public void UpdatePlacement(Placement placement)
217
    {
218
        _placement = placement;
2✔
219
        if (CompatibilityCheck())
2✔
220
        {
221
            BuildTable();
2✔
222
        }
223
    }
2✔
224

225
    /// <summary>
226
    /// Updates the type of the borders of the table.
227
    /// </summary>
228
    /// <param name="bordersType">The type of the borders of the table.</param>
229
    /// <remarks>
230
    /// For more information, consider visiting the documentation available <a href="https://morgankryze.github.io/ConsoleAppVisuals/">here</a>.
231
    /// </remarks>
232
    public void UpdateBordersType(BordersType bordersType)
233
    {
234
        _borders.UpdateBordersType(bordersType);
2✔
235
        if (CompatibilityCheck())
2✔
236
        {
237
            BuildTable();
1✔
238
        }
239
    }
2✔
240

241
    /// <summary>
242
    /// Updates the text of the footer.
243
    /// </summary>
244
    /// <param name="footerText">The text of the footer.</param>
245
    /// <remarks>
246
    /// For more information, consider visiting the documentation available <a href="https://morgankryze.github.io/ConsoleAppVisuals/">here</a>.
247
    /// </remarks>
248
    public void UpdateFooterText(string footerText)
249
    {
250
        _footerText = footerText;
2✔
251
        if (CompatibilityCheck())
2✔
252
        {
253
            BuildTable();
1✔
254
        }
255
    }
2✔
256

257
    /// <summary>
258
    /// Sets the table to exclude the header.
259
    /// </summary>
260
    /// <param name="excludeHeader">Whether to exclude the header or not.</param>
261
    /// <remarks>
262
    /// For more information, consider visiting the documentation available <a href="https://morgankryze.github.io/ConsoleAppVisuals/">here</a>.
263
    /// </remarks>
264
    public void SetExcludeHeader(bool excludeHeader = DEFAULT_EXCLUDE_HEADER)
265
    {
266
        _excludeHeader = excludeHeader;
2✔
267
        if (CompatibilityCheck())
2✔
268
        {
269
            BuildTable();
1✔
270
        }
271
    }
2✔
272

273
    /// <summary>
274
    /// Sets the table to exclude the footer.
275
    /// </summary>
276
    /// <param name="excludeFooter">Whether to exclude the footer or not.</param>
277
    /// <remarks>
278
    /// For more information, consider visiting the documentation available <a href="https://morgankryze.github.io/ConsoleAppVisuals/">here</a>.
279
    /// </remarks>
280
    public void SetExcludeFooter(bool excludeFooter = DEFAULT_EXCLUDE_FOOTER)
281
    {
282
        _excludeFooter = excludeFooter;
2✔
283
        if (CompatibilityCheck())
2✔
284
        {
285
            BuildTable();
1✔
286
        }
287
    }
2✔
288

289
    /// <summary>
290
    /// Adds headers to the table.
291
    /// </summary>
292
    /// <param name="headers">The headers to add.</param>
293
    /// <remarks>
294
    /// For more information, consider visiting the documentation available <a href="https://morgankryze.github.io/ConsoleAppVisuals/">here</a>.
295
    /// </remarks>
296
    public void AddHeaders(List<string> headers)
297
    {
298
        _rawHeaders = headers;
3✔
299
        if (CompatibilityCheck())
3✔
300
        {
301
            BuildTable();
2✔
302
        }
303
    }
2✔
304

305
    /// <summary>
306
    /// Updates the headers of the table.
307
    /// </summary>
308
    /// <param name="headers">The headers to update.</param>
309
    /// <remarks>
310
    /// For more information, consider visiting the documentation available <a href="https://morgankryze.github.io/ConsoleAppVisuals/">here</a>.
311
    /// </remarks>
312
    public void UpdateHeaders(List<string> headers)
313
    {
314
        AddHeaders(headers);
1✔
315
    }
1✔
316

317
    /// <summary>
318
    /// Adds a title to the table.
319
    /// </summary>
320
    /// <param name="title">The title to add.</param>
321
    /// <remarks>
322
    /// For more information, consider visiting the documentation available <a href="https://morgankryze.github.io/ConsoleAppVisuals/">here</a>.
323
    /// </remarks>
324
    public void AddTitle(string title)
325
    {
326
        _title = title;
2✔
327
        BuildTable();
2✔
328
    }
2✔
329

330
    /// <summary>
331
    /// Updates the title of the table.
332
    /// </summary>
333
    /// <param name="title">The title to update.</param>
334
    /// <remarks>
335
    /// For more information, consider visiting the documentation available <a href="https://morgankryze.github.io/ConsoleAppVisuals/">here</a>.
336
    /// </remarks>
337
    public void UpdateTitle(string title)
338
    {
339
        AddTitle(title);
1✔
340
    }
1✔
341
    #endregion
342

343
    #region Manipulation Methods
344
    /// <summary>
345
    /// Gets the specified line in the table.
346
    /// </summary>
347
    /// <param name="index">The index of the line to return.</param>
348
    /// <returns>The line at the specified index.</returns>
349
    /// <exception cref="ArgumentOutOfRangeException">Thrown when the index is out of range.</exception>
350
    /// <remarks>
351
    /// For more information, consider visiting the documentation available <a href="https://morgankryze.github.io/ConsoleAppVisuals/">here</a>.
352
    /// </remarks>
353
    public List<string> GetLine(int index)
354
    {
355
        if (index < 0 || index >= _rawLines?.Count)
4!
356
        {
357
            throw new ArgumentOutOfRangeException(nameof(index), "The index is out of range.");
3✔
358
        }
359
        return _rawLines![index];
1✔
360
    }
361

362
    /// <summary>
363
    /// Gets all the elements from a column given its index.
364
    /// </summary>
365
    /// <param name="index">The index of the column.</param>
366
    /// <returns>The elements of the column.</returns>
367
    /// <exception cref="ArgumentOutOfRangeException">Thrown when the index is out of range.</exception>
368
    /// <remarks>
369
    /// For more information, consider visiting the documentation available <a href="https://morgankryze.github.io/ConsoleAppVisuals/">here</a>.
370
    /// </remarks>
371
    public List<string>? GetColumnData(int index)
372
    {
373
        if (_rawLines is null)
6✔
374
        {
375
            return null;
1✔
376
        }
377

378
        if (index < 0 || index >= _rawLines[0].Count)
5✔
379
        {
380
            throw new ArgumentOutOfRangeException(nameof(index), "Invalid column index.");
3✔
381
        }
382

383
        List<string>? list = new();
2✔
384
        for (int i = 0; i < _rawLines.Count; i++)
16✔
385
        {
386
            list.Add(_rawLines[i][index]);
6✔
387
        }
388
        return list;
2✔
389
    }
390

391
    /// <summary>
392
    /// Gets all the elements from a column given its header.
393
    /// </summary>
394
    /// <param name="header">The header of the column.</param>
395
    /// <returns>The elements of the column.</returns>
396
    /// <exception cref="ArgumentException">Thrown when the header is invalid.</exception>
397
    /// <remarks>
398
    /// For more information, consider visiting the documentation available <a href="https://morgankryze.github.io/ConsoleAppVisuals/">here</a>.
399
    /// </remarks>
400
    public List<string>? GetColumnData(string header)
401
    {
402
        if (_rawHeaders is null)
4✔
403
        {
404
            return null;
1✔
405
        }
406
        else if (_rawLines is null)
3✔
407
        {
408
            return null;
1✔
409
        }
410
        if (!_rawHeaders.Contains(header))
2✔
411
        {
412
            throw new ArgumentException(nameof(header), "Invalid column header.");
1✔
413
        }
414

415
        return GetColumnData(_rawHeaders.IndexOf(header));
1✔
416
    }
417

418
    /// <summary>
419
    /// Adds a line to the table.
420
    /// </summary>
421
    /// <param name="line">The line to add.</param>
422
    /// <exception cref="ArgumentException">Thrown when the number of columns in the table is not consistent with itself or with the headers.</exception>
423
    /// <remarks>
424
    /// For more information, consider visiting the documentation available <a href="https://morgankryze.github.io/ConsoleAppVisuals/">here</a>.
425
    /// </remarks>
426
    public void AddLine(List<string> line)
427
    {
428
        if (_rawLines?.Count > 0 && line.Count != _rawLines[0].Count)
3!
429
        {
430
            throw new ArgumentException(
1✔
431
                "The number of columns in the table is not consistent with other lines."
1✔
432
            );
1✔
433
        }
434
        if (_rawHeaders is not null && line.Count != _rawHeaders.Count)
2✔
435
        {
436
            throw new ArgumentException(
1✔
437
                "The number of columns in the table is not consistent with the headers."
1✔
438
            );
1✔
439
        }
440
        _rawLines ??= new List<List<string>>();
1!
441
        _rawLines.Add(line);
1✔
442
        BuildTable();
1✔
443
    }
1✔
444

445
    /// <summary>
446
    /// Removes a line from the table given its index.
447
    /// </summary>
448
    /// <param name="index">The index of the line to remove.</param>
449
    /// <exception cref="ArgumentOutOfRangeException">Thrown when the index is out of range.</exception>
450
    /// <remarks>
451
    /// For more information, consider visiting the documentation available <a href="https://morgankryze.github.io/ConsoleAppVisuals/">here</a>.
452
    /// </remarks>
453
    public void RemoveLine(int index)
454
    {
455
        if (_rawLines?.Count > 0 && (index < 0 || index >= _rawLines.Count))
4!
456
        {
457
            throw new ArgumentOutOfRangeException(nameof(index), "The index is out of range.");
2✔
458
        }
459

460
        _rawLines?.RemoveAt(index);
2!
461
        BuildTable();
1✔
462
    }
1✔
463

464
    /// <summary>
465
    /// Updates a line in the table.
466
    /// </summary>
467
    /// <param name="index">The index of the line to update.</param>
468
    /// <param name="line">The new line.</param>
469
    /// <exception cref="ArgumentOutOfRangeException">Thrown when the index is out of range.</exception>
470
    /// <exception cref="ArgumentException">Thrown when the number of columns in the table is not consistent with itself or with the headers.</exception>
471
    /// <remarks>
472
    /// For more information, consider visiting the documentation available <a href="https://morgankryze.github.io/ConsoleAppVisuals/">here</a>.
473
    /// </remarks>
474
    public void UpdateLine(int index, List<string> line)
475
    {
476
        if (_rawLines?.Count > 0)
5!
477
        {
478
            if (index < 0 || index >= _rawLines.Count)
4✔
479
            {
480
                throw new ArgumentOutOfRangeException(nameof(index), "The index is out of range.");
2✔
481
            }
482

483
            if (line.Count != _rawHeaders?.Count)
2!
484
            {
485
                throw new ArgumentException(
1✔
486
                    "The number of columns in the table is not consistent."
1✔
487
                );
1✔
488
            }
489
        }
490
        _rawLines![index] = line;
2✔
491
        BuildTable();
1✔
492
    }
1✔
493

494
    /// <summary>
495
    /// Clears the headers of the table.
496
    /// </summary>
497
    /// <remarks>
498
    /// For more information, consider visiting the documentation available <a href="https://morgankryze.github.io/ConsoleAppVisuals/">here</a>.
499
    /// </remarks>
500
    public void ClearHeaders()
501
    {
502
        _rawHeaders = null;
1✔
503
        BuildTable();
1✔
504
    }
1✔
505

506
    /// <summary>
507
    /// Clears the lines of the table.
508
    /// </summary>
509
    /// <remarks>
510
    /// For more information, consider visiting the documentation available <a href="https://morgankryze.github.io/ConsoleAppVisuals/">here</a>.
511
    /// </remarks>
512
    public void ClearLines()
513
    {
514
        _rawLines = null;
1✔
515
        BuildTable();
1✔
516
    }
1✔
517

518
    /// <summary>
519
    /// Clears the table.
520
    /// </summary>
521
    /// <remarks>
522
    /// For more information, consider visiting the documentation available <a href="https://morgankryze.github.io/ConsoleAppVisuals/">here</a>.
523
    /// </remarks>
524
    public void Reset()
525
    {
526
        _title = null;
1✔
527
        _rawHeaders?.Clear();
1!
528
        _rawLines?.Clear();
1!
529
        _displayArray = null;
1✔
530
    }
1✔
531
    #endregion
532

533

534
    #region Rendering
535

536
    #region Build Methods
537
    private void BuildTable()
538
    {
539
        if (_rawHeaders is null)
64✔
540
        {
541
            if (_rawLines is not null)
5✔
542
            {
543
                BuildLines();
5✔
544
            }
545
        }
546
        else
547
        {
548
            if (_rawLines is null)
59✔
549
            {
550
                BuildHeaders();
4✔
551
            }
552
            else
553
            {
554
                BuildHeadersAndLines();
55✔
555
            }
556
        }
557
    }
55✔
558

559
    private void BuildHeadersAndLines()
560
    {
561
        if (_rawHeaders is not null && _rawLines is not null)
55✔
562
        {
563
            var stringList = new List<string>();
55✔
564
            var localMax = new int[_rawHeaders.Count];
55✔
565
            for (int i = 0; i < _rawHeaders.Count; i++)
660✔
566
            {
567
                if (_rawHeaders[i]?.Length > localMax[i])
275!
568
                {
569
                    localMax[i] = _rawHeaders[i]?.Length ?? 0;
275!
570
                }
571
            }
572

573
            for (int i = 0; i < _rawLines.Count; i++)
430✔
574
            {
575
                for (int j = 0; j < _rawLines[i].Count; j++)
1,920✔
576
                {
577
                    if (_rawLines[i][j]?.ToString()?.Length > localMax[j])
800!
578
                    {
579
                        localMax[j] = _rawLines[i][j]?.ToString()?.Length ?? 0;
36!
580
                    }
581
                }
582
            }
583

584
            StringBuilder headerBuilder = new($"{Borders.Vertical} ");
55✔
585
            for (int i = 0; i < _rawHeaders.Count; i++)
660✔
586
            {
587
                headerBuilder.Append(_rawHeaders[i]?.PadRight(localMax[i]) ?? "");
275!
588
                if (i != _rawHeaders.Count - 1)
275✔
589
                {
590
                    headerBuilder.Append($" {Borders.Vertical} ");
220✔
591
                }
592
                else
593
                {
594
                    headerBuilder.Append($" {Borders.Vertical}");
55✔
595
                }
596
            }
597
            stringList.Add(headerBuilder.ToString());
55✔
598

599
            StringBuilder upperBorderBuilder = new(Borders.TopLeft.ToString());
55✔
600
            for (int i = 0; i < _rawHeaders.Count; i++)
660✔
601
            {
602
                upperBorderBuilder.Append(new string(Borders.Horizontal, localMax[i] + 2));
275✔
603
                upperBorderBuilder.Append(
275✔
604
                    (i != _rawHeaders.Count - 1)
275✔
605
                        ? Borders.Top.ToString()
275✔
606
                        : Borders.TopRight.ToString()
275✔
607
                );
275✔
608
            }
609
            stringList.Insert(0, upperBorderBuilder.ToString());
55✔
610

611
            StringBuilder intermediateBorderBuilder = new(Borders.Left.ToString());
55✔
612
            for (int i = 0; i < _rawHeaders.Count; i++)
660✔
613
            {
614
                intermediateBorderBuilder.Append(new string(Borders.Horizontal, localMax[i] + 2));
275✔
615
                intermediateBorderBuilder.Append(
275✔
616
                    (i != _rawHeaders.Count - 1)
275✔
617
                        ? Borders.Cross.ToString()
275✔
618
                        : Borders.Right.ToString()
275✔
619
                );
275✔
620
            }
621
            stringList.Add(intermediateBorderBuilder.ToString());
55✔
622

623
            for (int i = 0; i < _rawLines.Count; i++)
430✔
624
            {
625
                StringBuilder lineBuilder = new($"{Borders.Vertical} ");
160✔
626
                for (int j = 0; j < _rawLines[i].Count; j++)
1,920✔
627
                {
628
                    lineBuilder.Append(_rawLines[i][j]?.ToString()?.PadRight(localMax[j]) ?? "");
800!
629
                    if (j != _rawLines[i].Count - 1)
800✔
630
                    {
631
                        lineBuilder.Append($" {Borders.Vertical} ");
640✔
632
                    }
633
                    else
634
                    {
635
                        lineBuilder.Append($" {Borders.Vertical}");
160✔
636
                    }
637
                }
638
                stringList.Add(lineBuilder.ToString());
160✔
639
            }
640

641
            StringBuilder lowerBorderBuilder = new(Borders.BottomLeft.ToString());
55✔
642
            for (int i = 0; i < _rawHeaders.Count; i++)
660✔
643
            {
644
                lowerBorderBuilder.Append(new string(Borders.Horizontal, localMax[i] + 2));
275✔
645
                lowerBorderBuilder.Append(
275✔
646
                    (i != _rawHeaders.Count - 1)
275✔
647
                        ? Borders.Bottom.ToString()
275✔
648
                        : Borders.BottomRight.ToString()
275✔
649
                );
275✔
650
            }
651
            stringList.Add(lowerBorderBuilder.ToString());
55✔
652

653
            _displayArray = stringList.ToArray();
55✔
654
            BuildTitle();
55✔
655
        }
656
    }
55✔
657

658
    private void BuildHeaders()
659
    {
660
        if (_rawHeaders is not null)
4✔
661
        {
662
            var stringList = new List<string>();
4✔
663
            var localMax = new int[_rawHeaders.Count];
4✔
664
            for (int i = 0; i < _rawHeaders.Count; i++)
48✔
665
            {
666
                if (_rawHeaders[i]?.Length > localMax[i])
20!
667
                {
668
                    localMax[i] = _rawHeaders[i]?.Length ?? 0;
20!
669
                }
670
            }
671
            StringBuilder headerBuilder = new($"{Borders.Vertical} ");
4✔
672
            for (int i = 0; i < _rawHeaders.Count; i++)
48✔
673
            {
674
                headerBuilder.Append(_rawHeaders[i]?.PadRight(localMax[i]) ?? "");
20!
675
                if (i != _rawHeaders.Count - 1)
20✔
676
                {
677
                    headerBuilder.Append($" {Borders.Vertical} ");
16✔
678
                }
679
                else
680
                {
681
                    headerBuilder.Append($" {Borders.Vertical}");
4✔
682
                }
683
            }
684
            stringList.Add(headerBuilder.ToString());
4✔
685
            StringBuilder upperBorderBuilder = new(Borders.TopLeft.ToString());
4✔
686
            for (int i = 0; i < _rawHeaders.Count; i++)
48✔
687
            {
688
                upperBorderBuilder.Append(new string(Borders.Horizontal, localMax[i] + 2));
20✔
689
                upperBorderBuilder.Append(
20✔
690
                    (i != _rawHeaders.Count - 1)
20✔
691
                        ? Borders.Top.ToString()
20✔
692
                        : Borders.TopRight.ToString()
20✔
693
                );
20✔
694
            }
695
            stringList.Insert(0, upperBorderBuilder.ToString());
4✔
696
            StringBuilder lowerBorderBuilder = new(Borders.BottomLeft.ToString());
4✔
697
            for (int i = 0; i < _rawHeaders.Count; i++)
48✔
698
            {
699
                lowerBorderBuilder.Append(new string(Borders.Horizontal, localMax[i] + 2));
20✔
700
                lowerBorderBuilder.Append(
20✔
701
                    (i != _rawHeaders.Count - 1)
20✔
702
                        ? Borders.Bottom.ToString()
20✔
703
                        : Borders.BottomRight.ToString()
20✔
704
                );
20✔
705
            }
706
            stringList.Add(lowerBorderBuilder.ToString());
4✔
707
            _displayArray = stringList.ToArray();
4✔
708
            BuildTitle();
4✔
709
        }
710
    }
4✔
711

712
    private void BuildLines()
713
    {
714
        if (_rawLines is not null)
5✔
715
        {
716
            var stringList = new List<string>();
5✔
717
            var localMax = new int[_rawLines[0].Count];
5✔
718
            for (int i = 0; i < _rawLines.Count; i++)
40✔
719
            {
720
                for (int j = 0; j < _rawLines[i].Count; j++)
180✔
721
                {
722
                    if (_rawLines[i][j]?.ToString()?.Length > localMax[j])
75!
723
                    {
724
                        localMax[j] = _rawLines[i][j]?.ToString()?.Length ?? 0;
35!
725
                    }
726
                }
727
            }
728
            for (int i = 0; i < _rawLines.Count; i++)
40✔
729
            {
730
                StringBuilder line = new($"{Borders.Vertical} ");
15✔
731
                for (int j = 0; j < _rawLines[i].Count; j++)
180✔
732
                {
733
                    line.Append(_rawLines[i][j]?.ToString()?.PadRight(localMax[j]) ?? "");
75!
734
                    if (j != _rawLines[i].Count - 1)
75✔
735
                    {
736
                        line.Append($" {Borders.Vertical} ");
60✔
737
                    }
738
                    else
739
                    {
740
                        line.Append($" {Borders.Vertical}");
15✔
741
                    }
742
                }
743
                stringList.Add(line.ToString());
15✔
744
            }
745
            StringBuilder upperBorderBuilder = new(Borders.TopLeft.ToString());
5✔
746
            for (int i = 0; i < _rawLines.Count; i++)
40✔
747
            {
748
                upperBorderBuilder.Append(new string(Borders.Horizontal, localMax[i] + 2));
15✔
749
                upperBorderBuilder.Append(
15✔
750
                    (i != _rawLines.Count - 1)
15✔
751
                        ? Borders.Top.ToString()
15✔
752
                        : Borders.TopRight.ToString()
15✔
753
                );
15✔
754
            }
755
            stringList.Insert(0, upperBorderBuilder.ToString());
5✔
756
            StringBuilder lowerBorderBuilder = new(Borders.BottomLeft.ToString());
5✔
757
            for (int i = 0; i < _rawLines.Count; i++)
40✔
758
            {
759
                lowerBorderBuilder.Append(new string(Borders.Horizontal, localMax[i] + 2));
15✔
760
                lowerBorderBuilder.Append(
15✔
761
                    (i != _rawLines.Count - 1)
15✔
762
                        ? Borders.Bottom.ToString()
15✔
763
                        : Borders.BottomRight.ToString()
15✔
764
                );
15✔
765
            }
766
            stringList.Add(lowerBorderBuilder.ToString());
5✔
767
            _displayArray = stringList.ToArray();
5✔
768
            BuildTitle();
5✔
769
        }
770
    }
5✔
771

772
    private void BuildTitle()
773
    {
774
        if (_title is not null)
64✔
775
        {
776
            var len = _displayArray![0].Length;
31✔
777
            var title = _title.ResizeString(len - 4);
31✔
778
            title = $"{Borders.Vertical} {title} {Borders.Vertical}";
31✔
779
            var upperBorderBuilder = new StringBuilder(Borders.TopLeft.ToString());
31✔
780
            upperBorderBuilder.Append(new string(Borders.Horizontal, len - 2));
31✔
781
            upperBorderBuilder.Append(Borders.TopRight.ToString());
31✔
782
            var display = _displayArray.ToList();
31✔
783
            display[0] = display[0]
31✔
784
                .Remove(0, 1)
31✔
785
                .Insert(0, Borders.Left.ToString())
31✔
786
                .Remove(display[1].Length - 1, 1)
31✔
787
                .Insert(display[1].Length - 1, Borders.Right.ToString());
31✔
788
            display.Insert(0, title);
31✔
789
            display.Insert(0, upperBorderBuilder.ToString());
31✔
790
            _displayArray = display.ToArray();
31✔
791
        }
792
    }
64✔
793
    #endregion
794

795
    /// <summary>
796
    /// Defines the actions to perform when the element is called to be rendered on the console.
797
    /// </summary>
798
    [Visual]
799
    protected override void RenderElementActions()
800
    {
801
        int minIndex = GetMinIndex(_excludeHeader);
802
        int maxIndex = GetMaxIndex(_excludeFooter);
803
        int index = minIndex;
804

805
        while (true)
806
        {
807
            string[] array = new string[_displayArray!.Length];
808
            for (int j = 0; j < _displayArray.Length; j++)
809
            {
810
                array[j] = _displayArray[j];
811
                Core.WritePositionedString(array[j], Placement, false, Line + j);
812
                if (j == index)
813
                {
814
                    Core.WritePositionedString(
815
                        j == _displayArray.Length - 1 ? " " + FooterText + " " : array[j][1..^1],
816
                        Placement,
817
                        true,
818
                        Line + j
819
                    );
820
                }
821
            }
822
            switch (Console.ReadKey(intercept: true).Key)
823
            {
824
                case ConsoleKey.UpArrow:
825
                case ConsoleKey.Z:
826
                    index = HandleUpArrowKey(index, minIndex, maxIndex);
827
                    break;
828
                case ConsoleKey.DownArrow:
829
                case ConsoleKey.S:
830
                    index = HandleDownArrowKey(index, minIndex, maxIndex);
831
                    break;
832
                case ConsoleKey.Enter:
833
                    SendResponse(
834
                        this,
835
                        new InteractionEventArgs<int>(Status.Selected, ReturnIndex(index))
836
                    );
837
                    return;
838
                case ConsoleKey.Escape:
839
                    SendResponse(this, new InteractionEventArgs<int>(Status.Escaped, 0));
840
                    return;
841
                case ConsoleKey.Backspace:
842
                    SendResponse(
843
                        this,
844
                        new InteractionEventArgs<int>(Status.Deleted, ReturnIndex(index))
845
                    );
846
                    return;
847
            }
848
        }
849

850
        [Visual]
851
        int ReturnIndex(int index)
852
        {
853
            return index - GetMinIndex(_excludeHeader);
854
        }
855

856
        [Visual]
857
        int GetMinIndex(bool excludeHeader)
858
        {
859
            if (excludeHeader)
860
            {
861
                return _rawHeaders is null ? 3 : 5;
862
            }
863
            else
864
            {
865
                return 0;
866
            }
867
        }
868

869
        [Visual]
870
        int GetMaxIndex(bool excludeFooter)
871
        {
872
            return excludeFooter ? _displayArray!.Length - 2 : _displayArray!.Length - 1;
873
        }
874

875
        [Visual]
876
        static int HandleUpArrowKey(int index, int minIndex, int maxIndex)
877
        {
878
            if (index == minIndex)
879
            {
880
                return maxIndex;
881
            }
882
            else if (index > minIndex)
883
            {
884
                return index - 1;
885
            }
886
            return index;
887
        }
888

889
        [Visual]
890
        static int HandleDownArrowKey(int index, int minIndex, int maxIndex)
891
        {
892
            if (index == maxIndex)
893
            {
894
                return minIndex;
895
            }
896
            else if (index < maxIndex)
897
            {
898
                return index + 1;
899
            }
900
            return index;
901
        }
902
    }
903

904
    #endregion
905
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc