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

b3b00 / InteractiveCLI / 25244566544

02 May 2026 05:15AM UTC coverage: 90.748% (-4.1%) from 94.849%
25244566544

Pull #14

github

web-flow
Merge 5f98ccd0e into 68b55b5e0
Pull Request #14: Textarea navigation

368 of 426 branches covered (86.38%)

Branch coverage included in aggregate %.

252 of 306 new or added lines in 2 files covered. (82.35%)

2 existing lines in 1 file now uncovered.

1064 of 1152 relevant lines covered (92.36%)

44.4 hits per line

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

80.17
/src/interactiveCLI/VirtualConsole.cs
1
using System;
2
using System.Collections.Generic;
3
using System.Linq;
4
using System.Text;
5

6
namespace interactiveCLI;
7

8
public class VirtualConsole
9
{
10
    private readonly IConsole _console;
11
    private int _startTop;
12
    private int _maxVisibleLines;
13
    
14
    public List<StringBuilder> Lines { get; private set; }
1,705✔
15
    public int CursorX { get; private set; } // Column within current line
1,137✔
16
    public int CursorY { get; private set; } // Line index in the buffer
1,089✔
17
    public int ViewTopY { get; private set; } // Buffer line currently at top of view
1,916✔
18
    public bool IsInsertMode { get; set; } = true;
207✔
19
    public int MaxLines { get; }
53✔
20

21
    public VirtualConsole(IConsole console, int maxLines = 0)
25✔
22
    {
25✔
23
        _console = console;
25✔
24
        _startTop = _console.CursorTop;
25✔
25
        MaxLines = maxLines;
25✔
26
        
27
        // Ensure we have at least 5 lines of space if possible
28
        int availableLines = _console.WindowHeight - _startTop;
25✔
29
        if (availableLines < 5 && _console.WindowHeight >= 5)
25✔
30
        {
2✔
31
            int scrollAmount = 5 - availableLines;
2✔
32
            for(int i=0; i<scrollAmount; i++)
2,014✔
33
            {
1,005✔
34
                 _console.SetCursorPosition(0, _console.WindowHeight - 1);
1,005✔
35
                 _console.WriteLine();
1,005✔
36
            }
1,005✔
37
            _startTop -= scrollAmount;
2✔
38
            if (_startTop < 0) _startTop = 0;
2!
39
        }
2✔
40

41
        _maxVisibleLines = _console.WindowHeight - _startTop;
25✔
42
        if (_maxVisibleLines < 1) _maxVisibleLines = 1;
25!
43

44
        Lines = new List<StringBuilder> { new StringBuilder() };
25✔
45
        CursorX = 0;
25✔
46
        CursorY = 0;
25✔
47
        ViewTopY = 0;
25✔
48
        
49
        Redraw();
25✔
50
    }
25✔
51

52
    private void SetConsoleCursor()
53
    {
305✔
54
        int screenY = _startTop + (CursorY - ViewTopY);
305✔
55
        if (screenY < _startTop) screenY = _startTop;
305!
56
        if (screenY >= _console.WindowHeight) screenY = _console.WindowHeight - 1;
305!
57
        
58
        try
59
        {
305✔
60
            _console.SetCursorPosition(CursorX, screenY);
305✔
61
        }
305✔
NEW
62
        catch { }
×
63
    }
305✔
64

65
    public void Redraw()
66
    {
73✔
67
        for (int i = 0; i < _maxVisibleLines; i++)
1,968✔
68
        {
911✔
69
            int bufferY = ViewTopY + i;
911✔
70
            int screenY = _startTop + i;
911✔
71
            
72
            try
73
            {
911✔
74
                _console.SetCursorPosition(0, screenY);
911✔
75
            }
911✔
NEW
76
            catch { continue; }
×
77

78
            if (bufferY < Lines.Count)
911✔
79
            {
181✔
80
                string lineText = Lines[bufferY].ToString();
181✔
81
                int paddingLen = Math.Max(0, _console.WindowWidth - lineText.Length - 1);
181✔
82
                string padding = paddingLen > 0 ? new string(' ', paddingLen) : "";
181!
83
                _console.Write(lineText + padding);
181✔
84
            }
181✔
85
            else
86
            {
730✔
87
                int paddingLen = Math.Max(0, _console.WindowWidth - 1);
730✔
88
                string padding = paddingLen > 0 ? new string(' ', paddingLen) : "";
730!
89
                _console.Write(padding);
730✔
90
            }
730✔
91
        }
911✔
92
        SetConsoleCursor();
73✔
93
    }
73✔
94

95
    public void RedrawLine(int y)
96
    {
191✔
97
        if (y >= ViewTopY && y < ViewTopY + _maxVisibleLines)
191!
98
        {
191✔
99
            int screenY = _startTop + (y - ViewTopY);
191✔
100
            try
101
            {
191✔
102
                _console.SetCursorPosition(0, screenY);
191✔
103
                string lineText = Lines[y].ToString();
191✔
104
                int paddingLen = Math.Max(0, _console.WindowWidth - lineText.Length - 1);
191✔
105
                string padding = paddingLen > 0 ? new string(' ', paddingLen) : "";
191!
106
                _console.Write(lineText + padding);
191✔
107
            }
191✔
NEW
108
            catch { }
×
109
            SetConsoleCursor();
191✔
110
        }
191✔
111
    }
191✔
112

113
    public void MoveLeft()
114
    {
11✔
115
        if (CursorX > 0)
11✔
116
        {
10✔
117
            CursorX--;
10✔
118
            SetConsoleCursor();
10✔
119
        }
10✔
120
        else if (CursorY > 0)
1!
121
        {
1✔
122
            CursorY--;
1✔
123
            CursorX = Lines[CursorY].Length;
1✔
124
            CheckScrollUp();
1✔
125
        }
1✔
126
        else
NEW
127
        {
×
NEW
128
            SetConsoleCursor();
×
NEW
129
        }
×
130
    }
11✔
131

132
    public void MoveRight()
133
    {
8✔
134
        if (CursorX < Lines[CursorY].Length)
8✔
135
        {
7✔
136
            CursorX++;
7✔
137
            SetConsoleCursor();
7✔
138
        }
7✔
139
        else if (CursorY < Lines.Count - 1)
1!
140
        {
1✔
141
            CursorY++;
1✔
142
            CursorX = 0;
1✔
143
            CheckScrollDown();
1✔
144
        }
1✔
145
        else
NEW
146
        {
×
NEW
147
            SetConsoleCursor();
×
NEW
148
        }
×
149
    }
8✔
150

151
    public void MoveUp()
152
    {
10✔
153
        if (CursorY > 0)
10✔
154
        {
8✔
155
            CursorY--;
8✔
156
            if (CursorX > Lines[CursorY].Length)
8!
NEW
157
            {
×
NEW
158
                CursorX = Lines[CursorY].Length;
×
NEW
159
            }
×
160
            CheckScrollUp();
8✔
161
        }
8✔
162
        else
163
        {
2✔
164
            SetConsoleCursor();
2✔
165
        }
2✔
166
    }
10✔
167

168
    public void MoveDown()
169
    {
2✔
170
        if (CursorY < Lines.Count - 1)
2✔
171
        {
1✔
172
            CursorY++;
1✔
173
            if (CursorX > Lines[CursorY].Length)
1!
NEW
174
            {
×
NEW
175
                CursorX = Lines[CursorY].Length;
×
NEW
176
            }
×
177
            CheckScrollDown();
1✔
178
        }
1✔
179
        else
180
        {
1✔
181
            SetConsoleCursor();
1✔
182
        }
1✔
183
    }
2✔
184

185
    public void MoveHome(bool ctrl)
186
    {
8✔
187
        if (ctrl)
8✔
188
        {
3✔
189
            CursorY = 0;
3✔
190
            CursorX = 0;
3✔
191
            if (ViewTopY != 0)
3!
NEW
192
            {
×
NEW
193
                ViewTopY = 0;
×
NEW
194
                Redraw();
×
NEW
195
            }
×
196
            else
197
            {
3✔
198
                SetConsoleCursor();
3✔
199
            }
3✔
200
        }
3✔
201
        else
202
        {
5✔
203
            CursorX = 0;
5✔
204
            SetConsoleCursor();
5✔
205
        }
5✔
206
    }
8✔
207

208
    public void MoveEnd(bool ctrl)
209
    {
3✔
210
        if (ctrl)
3✔
211
        {
1✔
212
            CursorY = Lines.Count - 1;
1✔
213
            CursorX = Lines[CursorY].Length;
1✔
214
            CheckScrollDown();
1✔
215
        }
1✔
216
        else
217
        {
2✔
218
            CursorX = Lines[CursorY].Length;
2✔
219
            SetConsoleCursor();
2✔
220
        }
2✔
221
    }
3✔
222

223
    private void CheckScrollUp()
224
    {
9✔
225
        if (CursorY < ViewTopY)
9✔
226
        {
1✔
227
            // when a top of screen and currently display line at line #0 is not the first line of the buffer: redraw starting form line -1 at top of screen.
228
            ViewTopY = CursorY;
1✔
229
            Redraw();
1✔
230
        }
1✔
231
        else
232
        {
8✔
233
            SetConsoleCursor();
8✔
234
        }
8✔
235
    }
9✔
236

237
    private void CheckScrollDown()
238
    {
3✔
239
        if (CursorY >= ViewTopY + _maxVisibleLines)
3!
NEW
240
        {
×
241
            // if last line is on bottom screen, do nothing. else redraw starting from line +1 at top.
NEW
242
            ViewTopY = CursorY - _maxVisibleLines + 1;
×
NEW
243
            Redraw();
×
NEW
244
        }
×
245
        else
246
        {
3✔
247
            SetConsoleCursor();
3✔
248
        }
3✔
249
    }
3✔
250

251
    public void Enter()
252
    {
46✔
253
        if (MaxLines > 0 && Lines.Count >= MaxLines)
46✔
254
            return;
1✔
255

256
        string remainder = Lines[CursorY].ToString().Substring(CursorX);
45✔
257
        Lines[CursorY].Length = CursorX;
45✔
258
        Lines.Insert(CursorY + 1, new StringBuilder(remainder));
45✔
259
        
260
        CursorY++;
45✔
261
        CursorX = 0;
45✔
262

263
        // "on enter key : if a position (height,0) insert an empty line after current line. and move global display up using a writeline. Tht is also to say that starting textarea display move one line up in the display"
264
        int screenY_new = _startTop + (CursorY - ViewTopY);
45✔
265
        if (screenY_new >= _console.WindowHeight)
45✔
266
        {
10✔
267
            if (_startTop > 0)
10!
NEW
268
            {
×
269
                try
NEW
270
                {
×
NEW
271
                    _console.SetCursorPosition(0, _console.WindowHeight - 1);
×
NEW
272
                    _console.WriteLine();
×
NEW
273
                }
×
NEW
274
                catch { }
×
NEW
275
                _startTop--;
×
NEW
276
                _maxVisibleLines = _console.WindowHeight - _startTop;
×
NEW
277
            }
×
278
            else
279
            {
10✔
280
                ViewTopY++;
10✔
281
            }
10✔
282
        }
10✔
283
        Redraw();
45✔
284
    }
46✔
285

286
    public void Backspace()
287
    {
9✔
288
        if (CursorX > 0)
9✔
289
        {
8✔
290
            CursorX--;
8✔
291
            Lines[CursorY].Remove(CursorX, 1);
8✔
292
            RedrawLine(CursorY);
8✔
293
        }
8✔
294
        else if (CursorY > 0)
1✔
295
        {
1✔
296
            string currentLineText = Lines[CursorY].ToString();
1✔
297
            Lines.RemoveAt(CursorY);
1✔
298
            CursorY--;
1✔
299
            CursorX = Lines[CursorY].Length;
1✔
300
            Lines[CursorY].Append(currentLineText);
1✔
301
            
302
            if (CursorY < ViewTopY)
1!
NEW
303
            {
×
NEW
304
                ViewTopY = CursorY;
×
NEW
305
            }
×
306
            Redraw();
1✔
307
        }
1✔
308
    }
9✔
309

310
    public void Delete()
311
    {
2✔
312
        if (CursorX < Lines[CursorY].Length)
2✔
313
        {
1✔
314
            Lines[CursorY].Remove(CursorX, 1);
1✔
315
            RedrawLine(CursorY);
1✔
316
        }
1✔
317
        else if (CursorY < Lines.Count - 1)
1✔
318
        {
1✔
319
            string nextLineText = Lines[CursorY + 1].ToString();
1✔
320
            Lines.RemoveAt(CursorY + 1);
1✔
321
            Lines[CursorY].Append(nextLineText);
1✔
322
            Redraw();
1✔
323
        }
1✔
324
    }
2✔
325

326
    public void InsertChar(char c)
327
    {
182✔
328
        if (IsInsertMode)
182!
329
        {
182✔
330
            Lines[CursorY].Insert(CursorX, c);
182✔
331
            CursorX++;
182✔
332
            RedrawLine(CursorY);
182✔
333
        }
182✔
334
        else
NEW
335
        {
×
NEW
336
            if (CursorX < Lines[CursorY].Length)
×
NEW
337
            {
×
NEW
338
                Lines[CursorY][CursorX] = c;
×
NEW
339
                CursorX++;
×
NEW
340
                RedrawLine(CursorY);
×
NEW
341
            }
×
342
            else
NEW
343
            {
×
NEW
344
                Lines[CursorY].Append(c);
×
NEW
345
                CursorX++;
×
NEW
346
                RedrawLine(CursorY);
×
NEW
347
            }
×
NEW
348
        }
×
349
    }
182✔
350

351
    public void Finish()
352
    {
11✔
353
        try
354
        {
11✔
355
            int bottomY = _startTop + (Lines.Count - ViewTopY);
11✔
356
            if (bottomY > _console.WindowHeight - 1)
11!
NEW
357
            {
×
NEW
358
                bottomY = _console.WindowHeight - 1;
×
NEW
359
            }
×
360
            _console.SetCursorPosition(0, bottomY);
11✔
361
            _console.WriteLine();
11✔
362
        }
11✔
NEW
363
        catch { }
×
364
    }
11✔
365
}
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

© 2026 Coveralls, Inc