Terminal - Adding some (slightly buggy) xterm features
[tpg/acess2.git] / Usermode / Applications / gui_terminal_src / display.c
1 /*
2  * Acess GUI Terminal
3  * - By John Hodge (thePowersGang)
4  *
5  * display.c
6  * - Abstract display manipulation methods
7  */
8 #define DEBUG   0
9 #include "include/display.h"
10 #include <acess/sys.h>  // _SysDebug
11 #include <stdlib.h>     // exit
12 #include <string.h>
13 #include <unicode.h>
14 #include <stdio.h>
15 #include <axwin3/axwin.h>
16 #include <axwin3/richtext.h>
17 #include <stdbool.h>
18 #include <assert.h>
19
20 #if DEBUG
21 # define DEBUGS(v...)   _SysDebug(v)
22 #else
23 # define DEBUGS(v...)   do{}while(0)
24 #endif
25
26 #define UNIMPLIMENTED() do{_SysDebug("UNIMPLIMENTED %s", __func__); exit(-1);}while(0)
27
28 static inline int MIN(int a, int b) { return (a < b ? a : b); }
29 static inline int MAX(int a, int b) { return (a > b ? a : b); }
30
31 // === EXTERN ==
32 extern tHWND    gMainWindow;
33
34 typedef struct sLine    tLine;
35
36 struct sLine {
37         char    *Data;
38         // TODO: Cache offsets to avoid scan-forward
39         size_t  Len;
40         size_t  Size;
41         bool    IsDirty;
42 };
43
44 struct sTerminal {
45         void    *TermState;
46         
47          int    ViewCols;
48          int    ViewRows;
49
50          int    ScrollTop;
51          int    ScrollRows;
52
53          int    CursorRow;
54          int    CursorCol;
55
56          int    SavedRow;
57          int    SavedCol;
58
59         size_t  CursorByte;
60
61         bool    bUsingAltBuf;
62         bool    bHaveSwappedBuffers;
63         
64          int    OtherBufRow;
65          int    OtherBufCol;
66
67         size_t  ViewOffset;     
68         size_t  TotalLines;
69         struct sLine    *PriBuf;
70         
71         struct sLine    *AltBuf;
72 };
73
74 // === PROTOTYPES ===
75 void    Display_int_SetCursor(tTerminal *Term, int Row, int Col);
76
77 // === GLOBALS ===
78 tTerminal       gMainBuffer;
79  int    giCurrentLine;
80  int    giCurrentLinePos;       // byte offset, not column
81  int    giCurrentCol;
82  int    giFirstDispLine;        // First displayed line
83  int    giFirstLine;    // Ring buffer start
84 char    **gasDisplayLines;
85  int    *gaiDisplayLineSizes;
86 char    *gabDisplayLinesDirty;
87
88 // === CODE ===
89 tTerminal *Display_Init(int Cols, int Lines, int ExtraScrollbackLines)
90 {
91         tTerminal       *term = &gMainBuffer;
92         term->ViewCols = Cols;
93         term->ViewRows = Lines;
94         term->TotalLines = Lines + ExtraScrollbackLines;
95         term->PriBuf = calloc( sizeof(tLine), (Lines + ExtraScrollbackLines) );
96         
97         AxWin3_RichText_SetLineCount(gMainWindow, Lines+ExtraScrollbackLines);
98         AxWin3_RichText_SetCursorType(gMainWindow, 1);  // TODO: enum
99         return term;
100 }
101
102 void *Display_GetTermState(tTerminal *Term) {
103         return Term->TermState;
104 }
105 void Display_SetTermState(tTerminal *Term, void *State) {
106         Term->TermState = State;
107 }
108
109 // Return the byte length of a single on-screen character
110 size_t _GetCharLength(size_t AvailLength, const char *Text, uint32_t *BaseCodepoint)
111 {
112         if( !AvailLength )
113                 return 0;
114         
115         size_t  charlen = ReadUTF8(Text, BaseCodepoint);
116         
117         while(charlen < AvailLength)
118         {
119                 uint32_t        cp;
120                 size_t size = ReadUTF8(Text+charlen, &cp);
121                 if( Unicode_IsPrinting(cp) )
122                         break;
123                 charlen += size;
124         }
125         
126         return charlen;
127 }
128
129 tLine *Display_int_GetCurLine(tTerminal *Term)
130 {
131          int    lineidx = Term->CursorRow + (Term->bUsingAltBuf ? 0 : Term->ViewOffset);
132         //_SysDebug("lineidx = %i", lineidx);
133         return (Term->bUsingAltBuf ? Term->AltBuf : Term->PriBuf) + lineidx;
134 }
135
136 size_t Display_int_PushCharacter(tTerminal *Term, size_t AvailLength, const char *Text)
137 {
138         tLine   *lineptr = Display_int_GetCurLine(Term);
139         uint32_t        cp;
140         size_t  charlen = _GetCharLength(AvailLength, Text, &cp);
141         bool    bOverwrite = Unicode_IsPrinting(cp);
142         
143         // Figure out how much we need to shift the stream
144          int    shift;
145         if( bOverwrite ) {
146                 //_SysDebug("GetCharLen(%i-%i, %p+%i, NULL)", lineptr->Len, Term->CursorByte,
147                 //      lineptr->Data, Term->CursorByte);
148                 if( Term->CursorByte )
149                         assert(lineptr->Data);
150                 size_t nextlen = _GetCharLength(
151                         lineptr->Len-Term->CursorByte,
152                         lineptr->Data+Term->CursorByte,
153                         NULL);
154                 shift = charlen - nextlen;
155         }
156         else {
157                 shift = charlen;
158         }
159         
160         // Ensure we have space enough
161         if( !lineptr->Data || shift > 0 ) {
162                 const size_t    size_step = 64;
163                 assert(shift > 0);
164                 size_t  newsize = (lineptr->Len+shift+1 + size_step-1) & ~(size_step-1);
165                 if( newsize > lineptr->Size ) {
166                         lineptr->Size = newsize;
167                         void *tmp = realloc(lineptr->Data, lineptr->Size);
168                         if( !tmp )      perror("Display_int_PushCharacter - realloc");
169                         //_SysDebug("realloc gave %p from %p for line %i", tmp, lineptr->Data,
170                         //      Term->CursorRow);
171                         lineptr->Data = tmp;
172                 }
173         }
174         
175         // Insert into stream
176         char    *base = lineptr->Data + Term->CursorByte;
177         if( Term->CursorByte == lineptr->Len ) {
178                 // No shifting needed
179         }
180         else if( shift >= 0 ) {
181                 size_t  bytes = lineptr->Len - (Term->CursorByte+shift);
182                 memmove(base+shift, base, bytes);
183         }
184         else {
185                 shift = -shift;
186                 size_t  bytes = lineptr->Len - (Term->CursorByte+shift);
187                 memmove(base, base+shift, bytes);
188         }
189         memcpy(base, Text, charlen);
190         lineptr->IsDirty = true;
191         lineptr->Len += shift;
192         lineptr->Data[lineptr->Len] = 0;        // NULL Terminate
193
194         Term->CursorByte += charlen;
195         
196         // HACKY: Prevents the CursorCol++ in Display_AddText from having an effect
197         if( !bOverwrite )
198                 Term->CursorCol --;
199         
200         return charlen;
201 }
202
203 void Display_AddText(tTerminal *Term, size_t Length, const char *UTF8Text)
204 {
205         DEBUGS("%i += %i '%.*s'", Term->CursorRow, Length, Length, UTF8Text);
206         while( Length > 0 )
207         {
208                 if( Term->CursorCol == Term->ViewCols ) {
209                         Display_Newline(Term, 1);
210                 }
211                 size_t used = Display_int_PushCharacter(Term, Length, UTF8Text);
212         
213                 Length -= used;
214                 UTF8Text += used;
215                 
216                 Term->CursorCol ++;
217         }
218 }
219
220 void Display_Newline(tTerminal *Term, bool bCarriageReturn)
221 {
222         // Going down!
223         if( Term->bUsingAltBuf )
224         {
225                 if( Term->CursorRow == Term->ScrollTop + Term->ScrollRows-1 ) {
226                         Display_ScrollDown(Term, 1);
227                 }
228                 else if( Term->CursorRow == Term->ViewRows-1 ) {
229                         if( Term->ScrollRows == 0 ) {
230                                 // Scroll entire buffer
231                                 Display_ScrollDown(Term, 1);
232                         }
233                         else {
234                                 // Don't go down a line
235                         }
236                 }
237                 else {
238                         // No scroll needed
239                         Term->CursorRow ++;
240                 }
241         }
242         else
243         {
244                 if( Term->CursorRow == Term->TotalLines-1 ) {
245                         Display_ScrollDown(Term, 1);
246                 }
247                 else {
248                         Term->CursorRow ++;
249                 }
250         }
251         
252         if( bCarriageReturn )
253         {
254                 Term->CursorByte = 0;
255                 Term->CursorCol = 0;
256         }
257         else
258         {
259                 tLine   *line = Display_int_GetCurLine(Term);
260                 
261                 Term->CursorByte = 0;
262                  int    old_col = Term->CursorCol;
263                 Term->CursorCol = 0;
264
265                 size_t  ofs = 0;
266                 while( Term->CursorCol < old_col && ofs < line->Len ) {
267                         ofs += _GetCharLength(line->Len-ofs, line->Data+ofs, NULL);
268                         Term->CursorCol ++;
269                 }
270                 Term->CursorByte = ofs;
271
272                 while( Term->CursorCol < old_col )
273                         Display_AddText(Term, 1, " ");
274         }
275 }
276
277 void Display_SetScrollArea(tTerminal *Term, int Start, int Count)
278 {
279         assert(Start >= 0);
280         assert(Count >= 0);
281         Term->ScrollTop = Start;
282         Term->ScrollRows = MIN(Count, Term->ViewRows - Start);
283 }
284
285 void Display_ScrollDown(tTerminal *Term, int Count)
286 {
287          int    top, max;
288         tLine   *buffer;
289         
290         if( Term->bUsingAltBuf )
291         {
292                 top = (Term->ScrollRows == 0 ? 0 : Term->ScrollTop);
293                 max = (Term->ScrollRows == 0 ? Term->ViewRows : Term->ScrollRows);
294                 buffer = Term->AltBuf;
295         }
296         else
297         {
298                 top = 0;
299                 max = Term->TotalLines;
300                 buffer = Term->PriBuf;
301         }
302         
303         assert(Count < max);
304         assert(Count > -max);
305
306         DEBUGS("Scroll %p %i-%i down by %i", buffer, top, max, Count);
307         
308         buffer += top;
309
310          int    clear_top, clear_max;
311         if( Count < 0 )
312         {
313                 // -ve: Scroll up, move buffer contents down
314                 Count = -Count;
315                 for( int i = max-Count; i < max; i ++ )
316                         free(buffer[i].Data);
317                 memmove(buffer+Count, buffer, (max-Count)*sizeof(*buffer));
318                 
319                 clear_top = 0;
320                 clear_max = Count;
321         }
322         else
323         {
324                 for( int i = 0; i < Count; i ++ )
325                         free(buffer[i].Data);
326                 memmove(buffer, buffer+Count, (max-Count)*sizeof(*buffer));
327                 clear_top = max-Count;
328                 clear_max = max;
329         }
330         // Clear exposed lines
331         for( int i = clear_top; i < clear_max; i ++ )
332         {
333                 buffer[i].Data = NULL;
334                 buffer[i].Len = 0;
335                 buffer[i].Size = 0;
336                 buffer[i].IsDirty = true;
337         }
338         // Send scroll command to GUI
339         AxWin3_RichText_ScrollRange(gMainWindow, top, max, Count);
340         
341         Display_int_SetCursor(Term, Term->CursorRow, Term->CursorCol);
342 }
343
344 void Display_SetCursor(tTerminal *Term, int Row, int Col)
345 {
346         assert(Row >= 0);
347         assert(Col >= 0);
348
349         DEBUGS("Set cursor R%i,C%i", Row, Col); 
350
351         if( !Term->bUsingAltBuf ) {
352                 _SysDebug("NOTE: Using \\e[%i;%iH outside of alternat buffer is undefined", Row, Col);
353         }
354         
355         // NOTE: This may be interesting outside of AltBuffer
356         Display_int_SetCursor(Term, Row, Col);  
357 }
358 void Display_int_SetCursor(tTerminal *Term, int Row, int Col)
359 {
360         Term->CursorRow = Row;
361         tLine   *line = Display_int_GetCurLine(Term);
362         size_t ofs = 0;
363          int    i;
364         for( i = 0; i < Col; i ++ )
365         {
366         
367                 size_t clen = _GetCharLength(line->Len-ofs, line->Data+ofs, NULL);
368                 if( clen == 0 ) {
369                         break;
370                 }
371                 ofs += clen;
372         }
373         Term->CursorCol = i;
374         Term->CursorByte = ofs;
375         // Move to exactly the column specified
376         for( ; i < Col; i ++ ) {
377                 Display_int_PushCharacter(Term, 1, " ");
378                 Term->CursorCol ++;
379         }
380 }
381
382 void Display_MoveCursor(tTerminal *Term, int RelRow, int RelCol)
383 {
384         if( RelRow != 0 )
385         {
386                 UNIMPLIMENTED();
387         }
388         
389         if( RelCol != 0 )
390         {
391                 int req_col = Term->CursorCol + RelCol;
392                 if( req_col < 0 )       req_col = 0;
393                 if( req_col > Term->ViewCols )  req_col = Term->ViewCols;
394
395                 tLine   *line = Display_int_GetCurLine(Term);
396                 size_t  ofs = 0;
397                 for( int i = 0; i < req_col; i ++ )
398                 {
399                         size_t clen = _GetCharLength(line->Len-ofs, line->Data+ofs, NULL);
400                         if( clen == 0 ) {
401                                 req_col = i;
402                                 break;
403                         }
404                         ofs += clen;
405                 }
406
407                 Term->CursorCol = req_col;
408                 Term->CursorByte = ofs;
409         }
410 }
411
412 void Display_SaveCursor(tTerminal *Term)
413 {
414         Term->SavedRow = Term->CursorRow;
415         Term->SavedCol = Term->CursorCol;
416 }
417 void Display_RestoreCursor(tTerminal *Term)
418 {
419         Display_SetCursor(Term, Term->SavedRow, Term->SavedCol);
420 }
421
422 void Display_ClearLine(tTerminal *Term, int Dir)        // 0: All, 1: Forward, -1: Reverse
423 {
424         if( Dir == 0 )
425         {
426                 tLine   *line = Display_int_GetCurLine(Term);
427                 // Completely clear line
428                 if( line->Data )
429                         free(line->Data);
430                 line->Data = NULL;
431                 line->IsDirty = true;
432                 Term->CursorCol = 0;
433                 Term->CursorByte = 0;
434         }
435         else if( Dir == 1 )
436         {
437                 // Forward clear (truncate)
438         }
439         else if( Dir == -1 )
440         {
441                 // Reverse clear (replace with spaces)
442         }
443         else
444         {
445                 // BUGCHECK
446         }
447 }
448
449 void Display_ClearLines(tTerminal *Term, int Dir)       // 0: All, 1: Forward, -1: Reverse
450 {
451         if( Dir == 0 )
452         {
453                 // Push giDisplayLines worth of empty lines
454                 // Move cursor back up by giDisplayLines
455         }
456         else if( Dir == 1 )
457         {
458                 // Push (giDisplayLines - (giCurrentLine-giFirstDispLine)) and reverse
459         }
460         else if( Dir == -1 )
461         {
462                 // Reverse clear (replace with spaces)
463         }
464         else
465         {
466                 // BUGCHECK
467         }
468 }
469
470 void Display_ResetAttributes(tTerminal *Term)
471 {
472         UNIMPLIMENTED();
473 }
474 void Display_SetForeground(tTerminal *Term, uint32_t RGB)
475 {
476         char    buf[7+1];
477         sprintf(buf, "\1%06x", RGB&0xFFFFFF);
478         Display_AddText(Term, 7, buf);
479 }
480
481 void Display_SetBackground(tTerminal *Term, uint32_t RGB)
482 {
483         char    buf[7+1];
484         sprintf(buf, "\2%06x", RGB&0xFFFFFF);
485         Display_AddText(Term, 7, buf);
486 }
487
488 void Display_Flush(tTerminal *Term)
489 {
490          int    viewOfs = (Term->bUsingAltBuf ? 0 : Term->ViewOffset);
491         tLine   *buffer = (Term->bUsingAltBuf ? Term->AltBuf : Term->PriBuf );
492         AxWin3_RichText_SetLineCount(gMainWindow, (Term->bUsingAltBuf ? Term->ViewRows : Term->TotalLines));
493         for( int i = 0; i < Term->ViewRows; i ++ )
494         {
495                  int    line = (viewOfs + i) % Term->TotalLines;
496                 tLine   *lineptr = &buffer[line];
497                 // Swapping buffers should cause a full resend
498                 if( !Term->bHaveSwappedBuffers && !lineptr->IsDirty )
499                         continue;
500                 DEBUGS("Line %i+%i %p '%.*s'", viewOfs, i, lineptr->Data, lineptr->Len, lineptr->Data);
501                 AxWin3_RichText_SendLine(gMainWindow, viewOfs + i,
502                         lineptr->Data ? lineptr->Data : "" );
503                 lineptr->IsDirty = false;
504         }
505         AxWin3_RichText_SetCursorPos(gMainWindow, Term->CursorRow, Term->CursorCol);
506         Term->bHaveSwappedBuffers = false;
507 }
508
509 void Display_ShowAltBuffer(tTerminal *Term, bool AltBufEnabled)
510 {
511         if( Term->bUsingAltBuf == AltBufEnabled )
512         {
513                 // Nothing to do, so do nothing
514                 return ;
515         }
516
517         int row = Term->OtherBufRow;
518         int col = Term->OtherBufCol;
519         Term->OtherBufRow = Term->CursorRow;
520         Term->OtherBufCol = Term->CursorCol;
521         
522         Term->bUsingAltBuf = AltBufEnabled;
523         Term->bHaveSwappedBuffers = true;
524         if( AltBufEnabled )
525         {
526                 if( !Term->AltBuf )
527                 {
528                         Term->AltBuf = calloc( sizeof(Term->AltBuf[0]), Term->ViewRows );
529                 }
530                 AxWin3_RichText_SetLineCount(gMainWindow, Term->ViewRows);
531         }
532         else
533         {
534                 AxWin3_RichText_SetLineCount(gMainWindow, Term->TotalLines);
535         }
536         Display_int_SetCursor(Term, row, col);
537 }
538
539 void Display_SetTitle(tTerminal *Term, const char *Title)
540 {
541         _SysDebug("TODO: Set window title to '%s'", Title);
542 }
543

UCC git Repository :: git.ucc.asn.au