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