1 module jcli.text.helptext;
2 
3 import jcli.text;
4 
5 struct HelpTextDescription
6 {
7     uint indent;
8     string text;
9 }
10 
11 struct HelpText
12 {
13 
14     enum ARG_NAME_PERCENT = 0.1;
15     enum ARG_GUTTER_PERCENT = 0.05;
16     enum ARG_DESC_PERCENT = 0.3;
17 
18     private
19     {
20         TextBuffer  _text;
21         uint        _argNameWidth;
22         uint        _argDescWidth;
23         uint        _argGutterWidth;
24         uint        _rowCursor;
25         string      _cached;
26     }
27 
28     static HelpText make(uint width)
29     {
30         import std.math : round;
31 
32         HelpText t;
33         t._text = new TextBuffer(width, TextBuffer.AUTO_GROW);
34         t._argNameWidth = cast(uint)((cast(double)width) * ARG_NAME_PERCENT).round;
35         t._argDescWidth = cast(uint)((cast(double)width) * ARG_DESC_PERCENT).round;
36         t._argGutterWidth = cast(uint)((cast(double)width) * ARG_GUTTER_PERCENT).round;
37         return t;
38     }
39 
40     void addLine(string text)
41     {
42         Vector _1;
43         this._text.setString(
44             Rect(0, this._rowCursor, this._text.width, this._rowCursor+1),
45             text,
46             _1,
47         );
48         this._rowCursor++;
49     }
50 
51     void addLineWithPrefix(string prefix, string text, AnsiStyleSet prefixStyle = AnsiStyleSet.init)
52     {
53         import std.conv : to;
54 
55         Vector lastChar;
56         this._text.setString(
57             Rect(0, this._rowCursor, prefix.length.to!int, this._rowCursor + 1),
58             prefix,
59             lastChar,
60             prefixStyle
61         );
62         this._text.setString(
63             Rect(lastChar.x + 1, lastChar.y, this._text.width, this._rowCursor + 1),
64             text,
65             lastChar
66         );
67         this._rowCursor = lastChar.y + 1;
68     }
69 
70     void addHeaderWithText(string header, string text)
71     {
72         Vector lastChar;
73         this._text.setString(
74             Rect(0, this._rowCursor, this._text.width, this._rowCursor + 1),
75             header,
76             lastChar,
77             AnsiStyleSet.init.style(AnsiStyle.init.bold)
78         );
79         this._rowCursor++;
80         this._text.setString(
81             Rect(4, this._rowCursor, this._text.width, this._text.height),
82             text,
83             lastChar
84         );
85         this._rowCursor = lastChar.y + 2;
86     }
87 
88     void addHeader(string header)
89     {
90         Vector _1;
91         this._text.setString(
92             Rect(0, this._rowCursor, this._text.width, this._rowCursor + 1),
93             header,
94             _1,
95             AnsiStyleSet.init.style(AnsiStyle.init.bold)
96         );
97         this._rowCursor += 1;
98     }
99 
100     void addArgument(string name, HelpTextDescription[] description)
101     {
102         import std.math : round, ceil;
103         import std.algorithm.comparison : max;
104         
105         Vector namePos;
106         this._text.setString(
107             Rect(4, this._rowCursor, this._argNameWidth, this._rowCursor + 1),
108             name,
109             namePos
110         );
111         
112         Vector descPos = Vector(0, this._rowCursor);
113         foreach(desc; description)
114         {
115             const indent = desc.indent * 4;
116             this._text.setString(
117                 Rect(
118                     this._argNameWidth + this._argGutterWidth + indent, 
119                     descPos.y, 
120                     this._argNameWidth + this._argGutterWidth + this._argDescWidth, 
121                     descPos.y + cast(uint)(cast(float)this._argDescWidth / cast(float)desc.text.length).ceil + 1
122                 ),
123                 desc.text,
124                 descPos
125             );
126         }
127         this._rowCursor = max(namePos.y + 1, descPos.y + 1);
128     }
129 
130     string finish()
131     {
132         if(this._cached)
133             return this._cached;
134 
135         import std.array : Appender;
136         Appender!(char[]) output;
137         this._text.onRefresh = (row, cells)
138         {
139             AnsiStyleSet style;
140             foreach(cell; cells)
141             {
142                 if(cell.style != style && g_jcliTextUseColour)
143                 {
144                     style = cell.style;
145                     output.put(ANSI_COLOUR_RESET);
146                     output.put(ANSI_CSI);
147 
148                     char[AnsiStyleSet.MAX_CHARS_NEEDED] chars;
149                     output.put(style.toSequence(chars));
150                     output.put(ANSI_COLOUR_END);
151                 }
152                 output.put(cell.ch[0..cell.chLen]);
153             }
154             output.put(ANSI_COLOUR_RESET);
155             output.put('\n');
156         };
157         this._text.refresh();
158         import std.exception : assumeUnique;
159         this._cached = output.data.assumeUnique;
160         return this._cached;
161     }
162 }