File: pseudoterminaldlg.pp

package info (click to toggle)
lazarus 2.0.0%2Bdfsg-2
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 214,460 kB
  • sloc: pascal: 1,862,622; xml: 265,709; cpp: 56,595; sh: 3,008; java: 609; makefile: 535; perl: 297; sql: 222; ansic: 137
file content (813 lines) | stat: -rw-r--r-- 31,058 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
{              ------------------------------------------------
                PseudoTerminalDlg.pp  -  Debugger helper class
               ------------------------------------------------

  This unit supports a form with a window acting as the console of a
  program being debugged, in particular in manages resize events.


 ***************************************************************************
 *                                                                         *
 *   This source is free software; you can redistribute it and/or modify   *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This code is distributed in the hope that it will be useful, but      *
 *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
 *   General Public License for more details.                              *
 *                                                                         *
 *   A copy of the GNU General Public License is available on the World    *
 *   Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also      *
 *   obtain it by writing to the Free Software Foundation,                 *
 *   Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA.   *
 *                                                                         *
 ***************************************************************************
}

unit PseudoTerminalDlg;
{$IFDEF linux} {$DEFINE DBG_ENABLE_TERMINAL} {$ENDIF}


{$mode objfpc}{$H+}

interface

uses
  IDEWindowIntf, Classes, Graphics, Forms, StdCtrls, DebuggerDlg,
  BaseDebugManager, LazarusIDEStrConsts, LCLType, ComCtrls, ExtCtrls, MaskEdit,
  PairSplitter;

type

  { TPseudoConsoleDlg }

  TPseudoConsoleDlg = class(TDebuggerDlg)
    CheckGroupRight: TCheckGroup;
    GroupBoxRight: TGroupBox;
    MaskEdit1: TMaskEdit;
    Memo1: TMemo;
    PageControl1: TPageControl;
    PairSplitterRaw: TPairSplitter;
    PairSplitterRawLeft: TPairSplitterSide;
    PairSplitterRawRight: TPairSplitterSide;
    Panel1: TPanel;
    PanelRightBelowRG: TPanel;
    PanelRightBelowCG: TPanel;
    RadioGroupRight: TRadioGroup;
    StatusBar1: TStatusBar;
    TabSheet1: TTabSheet;
    TabSheetRaw: TTabSheet;
    procedure FormResize(Sender: TObject);
    procedure Memo1UTF8KeyPress(Sender: TObject; var UTF8Key: TUTF8Char);
    procedure PairSplitterRawRightResize(Sender: TObject);
    procedure RadioGroupRightSelectionChanged(Sender: TObject);
  private
    { private declarations }
    ttyHandle: System.THandle;         (* Used only by unix for console size tracking  *)
    fCharHeight: word;
    fCharWidth: word;
    fRowsPerScreen: integer;
    fColsPerRow: integer;
    fFirstLine: integer;
    procedure getCharHeightAndWidth(consoleFont: TFont; out h, w: word);
    procedure consoleSizeChanged;
  protected
    procedure DoClose(var CloseAction: TCloseAction); override;
  public
    { public declarations }
    constructor Create(TheOwner: TComponent); override;
    procedure AddOutput(const AText: String);
    procedure Clear;
    property CharHeight: word read fCharHeight;
    property CharWidth: word read fCharWidth;
    property RowsPerScreen: integer read fRowsPerScreen;
    property ColsPerRow: integer read fColsPerRow;
  end;

var
  PseudoConsoleDlg: TPseudoConsoleDlg;


implementation

uses
  SysUtils, StrUtils, LazLoggerBase
{$IFDEF DBG_ENABLE_TERMINAL}
  , Unix, BaseUnix, termio
{$ENDIF DBG_ENABLE_TERMINAL}
  ;

const
  handleUnopened= THandle(-$80000000);

var
  //DBG_VERBOSE,
  DBG_WARNINGS: PLazLoggerLogGroup;
  PseudoTerminalDlgWindowCreator: TIDEWindowCreator;

{ TPseudoConsoleDlg }

procedure TPseudoConsoleDlg.Memo1UTF8KeyPress(Sender: TObject;
  var UTF8Key: TUTF8Char);
begin
  DebugBoss.DoSendConsoleInput(Utf8Key);
  Utf8Key := '';
end;


procedure TPseudoConsoleDlg.PairSplitterRawRightResize(Sender: TObject);

var
  ttyNotYetInitialised: boolean;

begin

(* These are not errors so much as conditions we will see while the IDE is      *)
(* starting up.                                                                 *)

  if DebugBoss = nil then
    exit;
  if DebugBoss.PseudoTerminal = nil then
    exit;

(* Even if the IDE is initialised this can be called before the TTY is set up,  *)
(* so while we prefer success we also consider that failure /is/ an acceptable  *)
(* option in this case.                                                         *)

  ttyNotYetInitialised := ttyHandle = handleUnopened;
  //DebugLn(DBG_VERBOSE, ['TPseudoConsoleDlg.SplitterRawRightResize Calling consoleSizeChanged']);
  consoleSizeChanged;
  if ttyNotYetInitialised and (integer(ttyHandle) < 0) then begin
    DebugLn(DBG_WARNINGS, ['TPseudoConsoleDlg.SplitterRawRightResize Bad PseudoTerminal -> unopened']);
    ttyHandle := handleUnopened
  end;
  StatusBar1.Panels[3].Text := 'Splitter resized'
end { TPseudoConsoleDlg.PairSplitterRawRightResize } ;


(* The C1 underbar decoration is only relevant when C0 is being displayed as
  control pictures or ISO 2047 glyphs.
*)
procedure TPseudoConsoleDlg.RadioGroupRightSelectionChanged(Sender: TObject);

begin
  case RadioGroupRight.ItemIndex of
    1, 2: CheckGroupRight.CheckEnabled[1] := true
  otherwise
    CheckGroupRight.CheckEnabled[1] := false
  end
end { TPseudoConsoleDlg.RadioGroupRightSelectionChanged } ;


(* The form size has changed. Call a procedure to pass this to the kernel etc.,
  assuming that this works out the best control to track.
*)
procedure TPseudoConsoleDlg.FormResize(Sender: TObject);

var
  ttyNotYetInitialised: boolean;

begin

(* These are not errors so much as conditions we will see while the IDE is      *)
(* starting up.                                                                 *)

  if DebugBoss = nil then
    exit;
  if DebugBoss.PseudoTerminal = nil then
    exit;

(* Even if the IDE is initialised this can be called before the TTY is set up,  *)
(* so while we prefer success we also consider that failure /is/ an acceptable  *)
(* option in this case.                                                         *)

  ttyNotYetInitialised := ttyHandle = handleUnopened;
  //DebugLn(DBG_VERBOSE, ['TPseudoConsoleDlg.FormResize Calling consoleSizeChanged']);
  consoleSizeChanged;
  if ttyNotYetInitialised and (integer(ttyHandle) < 0) then begin
    DebugLn(DBG_WARNINGS, ['TPseudoConsoleDlg.FormResize Bad PseudoTerminal -> unopened']);
    ttyHandle := handleUnopened
  end;
  StatusBar1.Panels[3].Text := 'Window resized'
end { TPseudoConsoleDlg.FormResize } ;


procedure TPseudoConsoleDlg.DoClose(var CloseAction: TCloseAction);

begin
{$IFDEF DBG_ENABLE_TERMINAL}
  if integer(ttyHandle) >= 0 then begin
    FileClose(ttyHandle);
    ttyHandle := handleUnopened
  end;
{$ENDIF DBG_ENABLE_TERMINAL}
  inherited DoClose(CloseAction);
  CloseAction := caHide;
end { TPseudoConsoleDlg.DoClose } ;


constructor TPseudoConsoleDlg.Create(TheOwner: TComponent);

begin
  inherited Create(TheOwner);
  Caption:= lisDbgTerminal;
  ttyHandle := handleUnopened;
  fRowsPerScreen := -1;
  fColsPerRow := -1;
  fFirstLine := 1
end { TPseudoConsoleDlg.Create } ;


(* Get the height and width for characters described by the fount specified by
  the first parameter. This will normally be monospaced, but in case it's not
  use "W" which is normally the widest character in a typeface so that a
  subsequent conversion from a window size in pixels to one in character cells
  errs on the side of fewer rather than more rows and columns.
*)
procedure TPseudoConsoleDlg.getCharHeightAndWidth(consoleFont: TFont; out h, w: word);

var
  bm: TBitMap;

begin
  bm := TBitmap.Create;
  try
    bm.Canvas.Font.Assign(consoleFont);
    h := bm.Canvas.TextHeight('W');
    w := bm.Canvas.TextWidth('W')
  finally
    bm.Free
  end
end { TPseudoConsoleDlg.getCharHeightAndWidth } ;


(* Assume that the console size has changed, either because it's just starting
  to be used or because a window has been resized. Use an ioctl() to tell a TTY
  to reconsider its opinion of itself, and if necessary send an explicit signal
  to the process being debugged. Assume that this is peculiar to unix-like OSes,
  but may be called safely by others.
*)
procedure TPseudoConsoleDlg.consoleSizeChanged;

{$IFDEF DBG_ENABLE_TERMINAL}
{ DEFINE USE_SLAVE_HANDLE }
{ DEFINE SEND_EXPLICIT_SIGNAL }

var
{$IFDEF USE_SLAVE_HANDLE }
  s: string;
{$ENDIF USE_SLAVE_HANDLE }
  winSize: TWinSize;

begin
  if ttyHandle = handleUnopened then

(* Assume that we get here when the first character is to be written by the     *)
(* program being debugged, and that the form and memo are fully initialised.    *)
(* Leave ttyHandle either open (i.e. >= 0) or -ve but no longer handleUnopened, *)
(* in the latter case no further attempt will be made to use it.                *)

// Requires -dDBG_WITH_DEBUGGER_DEBUG

    if DebugBoss.PseudoTerminal <> nil then begin
      //DebugLn(DBG_VERBOSE, ['TPseudoConsoleDlg.AddOutput PseudoTerminal.DevicePtyMaster=',
      //                  DebugBoss.PseudoTerminal.DevicePtyMaster]);
{$IFDEF USE_SLAVE_HANDLE }
      s := DebugBoss.PseudoTerminal.Devicename;
      //DebugLn(DBG_VERBOSE, ['TPseudoConsoleDlg.AddOutput PseudoTerminal.Devicename="', s, '"']);
      ttyHandle := fileopen(s, fmOpenWrite)
{$ELSE                   }
      ttyHandle := DebugBoss.PseudoTerminal.DevicePtyMaster;
{$ENDIF USE_SLAVE_HANDLE }
      //DebugLn(DBG_VERBOSE, ['TPseudoConsoleDlg.AddOutput ttyHandle=', ttyHandle]);
      getCharHeightAndWidth(Memo1.Font, fCharHeight, fCharWidth)
    end else begin                      (* Can't get pseudoterminal             *)
      DebugLn(DBG_WARNINGS, ['TPseudoConsoleDlg.AddOutput Unopened -> bad PseudoTerminal']);
      ttyHandle := THandle(-1)
    end;

(* Every time we're called, provided that we were able to open the TTY, work    *)
(* out the window size and tell the kernel and/or process.                      *)

  if integer(ttyHandle) >= 0 then begin (* Got slave TTY name and valid handle  *)
    with winSize do begin
      ws_xpixel := Memo1.ClientWidth;
      ws_ypixel := Memo1.ClientHeight;      (* Assume the font is monospaced         *)
      ws_row := ws_ypixel div fCharHeight;
      ws_col := ws_xpixel div fCharwidth;
      //DebugLn(DBG_VERBOSE, ['TPseudoConsoleDlg.AddOutput (rows x cols)=(', ws_row, ' x ', ws_col, ')']);

(* TIOCGWINSZ reports the console size in both character cells and pixels, but  *)
(* since we're not likely to be emulating e.g. a Tektronix terminal or one of   *)
(* the higher-end DEC ones it's reasonable to bow out here if the size hasn't   *)
(* changed by at least a full row or character.                                 *)

      if (ws_row = fRowsPerScreen) and (ws_col = fColsPerRow) then
        exit;
      fRowsPerScreen := ws_row;
      fColsPerRow := ws_col
    end;

(* Note that when the Linux kernel (or appropriate driver etc.) gets TIOCSWINSZ *)
(* it takes it upon itself to raise a SIGWINCH, I've not tested whether other   *)
(* unix implementations do the same. Because this is an implicit action, and    *)
(* because by and large the process receiving the signal can identify the       *)
(* sender and would be entitled to be unhappy if the sender appeared to vary,   *)
(* I've not attempted to defer signal sending in cases where the process being  *)
(* debugged is in a paused state or is otherwise suspected to not be able to    *)
(* handle it immediately. MarkMLl (so you know who to kick).                    *)

    if fpioctl(ttyHandle, TIOCSWINSZ, @winSize) < 0 then begin
      fileclose(ttyHandle);
      DebugLn(DBG_WARNINGS, ['TPseudoConsoleDlg.AddOutput Write failed, closed handle']);
      ttyHandle := THandle(-1)      (* Attempted ioctl() failed                 *)
    end;
    if integer(ttyHandle) >= 0 then begin (* Handle not closed by error         *)
{$IFDEF SEND_EXPLICIT_SIGNAL }
{$WARNING TPseudoConsoleDlg.consoleSizeChanged: Explicit signal untested }

// If I'm reading things correctly ReqCmd() is private, so this needs fettling.
// Need to introduce DebugBoss.SendSignal and Debugger.SendSignal

      //DebugBoss.Debugger.ReqCmd(dcSendSignal, [SIGWINCH]);
{$ENDIF SEND_EXPLICIT_SIGNAL }
      FillChar(winSize, sizeof(winSize), 0); (* Did it work?                    *)
      fpioctl(ttyHandle, TIOCGWINSZ, @winSize);
      //DebugLn(DBG_VERBOSE, ['TPseudoConsoleDlg.AddOutput readback=(', winSize.ws_row, ' x ', winSize.ws_col, ')'])
    end
  end;
{$ELSE       }
begin
  ttyHandle := THandle(-1);             (* Not used in non-unix OSes            *)
{$ENDIF DBG_ENABLE_TERMINAL}
  Assert(ttyHandle <> handleUnopened, 'TPseudoConsoleDlg.consoleSizeChanged: TTY handle still in virgin state at exit');
  RadioGroupRightSelectionChanged(nil); (* Sort out initial state               *)
  StatusBar1.Panels[0].Width := Width div 4;
  StatusBar1.Panels[0].Text := '    ' ; // + DebugBoss.Debugger.Environment.Values['TERM'];
  StatusBar1.Panels[1].Width := Width div 4;
{$IFDEF DBG_ENABLE_TERMINAL}
  StatusBar1.Panels[1].Text := Format('%d cols x %d rows', [winsize.ws_col, winsize.ws_row]);
{$ENDIF DBG_ENABLE_TERMINAL}
  StatusBar1.Panels[2].Width := Width div 4;
{$IFDEF DBG_ENABLE_TERMINAL}
  StatusBar1.Panels[2].Text := Format('%d x %d pixels', [winsize.ws_xpixel, winsize.ws_ypixel])
{$ENDIF DBG_ENABLE_TERMINAL}
end { TPseudoConsoleDlg.consoleSizeChanged } ;


procedure TPseudoConsoleDlg.AddOutput(const AText: String);

var
  lineLimit, numLength, i: integer;
  buffer: TStringList;


  (* Translate C0 control codes to "control pictures", and optionally C1 codes
    to the same glyph but with an underbar.
  *)
  function withControlPictures(const str: string; c1Underbar: boolean): widestring;

  const
    nul= #$2400;                        // ␀
    soh= #$2401;                        // ␁
    stx= #$2402;                        // ␂
    etx= #$2403;                        // ␃
    eot= #$2404;                        // ␄
    enq= #$2405;                        // ␅
    ack= #$2406;                        // ␆
    bel= #$2407;                        // ␇
    bs=  #$2408;                        // ␈
    ht=  #$2409;                        // ␉
    lf=  #$240a;                        // ␊
    vt=  #$240b;                        // ␋
    ff=  #$240c;                        // ␌
    cr=  #$240d;                        // ␍
    so=  #$240e;                        // ␎
    si=  #$240f;                        // ␏
    dle= #$2410;                        // ␐
    dc1= #$2411;                        // ␑
    dc2= #$2412;                        // ␒
    dc3= #$2413;                        // ␓
    dc4= #$2414;                        // ␔
    nak= #$2415;                        // ␕
    syn= #$2416;                        // ␖
    etb= #$2417;                        // ␗
    can= #$2418;                        // ␘
    em=  #$2419;                        // ␙
    sub= #$241a;                        // ␚
    esc= #$241b;                        // ␛
    fs=  #$241c;                        // ␜
    gs=  #$241d;                        // ␝
    rs=  #$241e;                        // ␞
    us=  #$241f;                        // ␟
    del= #$2420;                        // ␡
    bar= #$033c;                        // ̼'

  var
    i, test, masked: integer;
    changed: boolean;

  begin
    SetLength(result, Length(str));

  (* This should probably be recoded to use a persistent table, but doing it    *)
  (* this way results in no lookup for plain text which is likely to be the     *)
  (* bulk of the output. I'm not making any assumptions about the Unicode       *)
  (* characters being sequential so that this code can be used both for control *)
  (* pictures and ISO-2047 glyphs, and so that if somebody has (good) reason to *)
  (* want to adjust them he can do so.                                          *)

    for i := Length(str) downto 1 do begin
      test := Ord(str[i]);
      if c1Underbar then
        masked := test and $7f          (* Handle both C0 and C1 in one operation *)
      else
        masked := test;
      changed := true;
      case masked of
        $00: result[i] := nul;
        $01: result[i] := soh;
        $02: result[i] := stx;
        $03: result[i] := etx;
        $04: result[i] := eot;
        $05: result[i] := enq;
        $06: result[i] := ack;
        $07: result[i] := bel;
        $08: result[i] := bs;
        $09: result[i] := ht;
        $0a: result[i] := lf;
        $0b: result[i] := vt;
        $0c: result[i] := ff;
        $0d: result[i] := cr;
        $0e: result[i] := so;
        $0f: result[i] := si;
        $10: result[i] := dle;
        $11: result[i] := dc1;
        $12: result[i] := dc2;
        $13: result[i] := dc3;
        $14: result[i] := dc4;
        $15: result[i] := nak;
        $16: result[i] := syn;
        $17: result[i] := etb;
        $18: result[i] := can;
        $19: result[i] := em;
        $1a: result[i] := sub;
        $1b: result[i] := esc;
        $1c: result[i] := fs;
        $1d: result[i] := gs;
        $1e: result[i] := rs;
        $1f: result[i] := us;
        $7f: result[i] := del
      otherwise
        result[i] := Chr(test);
        changed := false;
      end;
      if c1Underbar and changed and     (* Now fix changed C1 characters        *)
                                (masked <> test) then
        Insert(bar, result, i)
    end
  end { withControlPictures } ;


  (* Translate C0 control codes to "pretty pictures", and optionally C1 codes
    to the same glyph but with an underbar.
  *)
  function withIso2047(const str: string; c1Underbar: boolean): widestring;

  (* I've not got access to a pukka copy of ISO-2047, so like (it appears)      *)
  (* almost everybody else I'm assuming that the Wikipedia page is correct.     *)
  (* this differs from the ECMA standard (only) in the backspace glyph, some    *)
  (* terminals in particular the Burroughs TD730/830 range manufactured in the  *)
  (* 1970s and 1980s depart slightly more. I've found limited open source       *)
  (* projects that refer to this encoding, and those I've found have attempted  *)
  (* to "correct" details like the "direction of rotation" of the glyphs for    *)
  (* the DC1 through DC4 codes.                                                 *)
  (*                                                                            *)
  (* Suffixes W, E and B below refer to the variants found in the Wikipedia     *)
  (* article, the ECMA standard and the Burroughs terminal documentation.       *)

  const
    nul=  #$2395;                       // ⎕
    soh=  #$2308;                       // ⌈
    stx=  #$22A5;                       // ⊥
    etx=  #$230B;                       // ⌋
    eot=  #$2301;                       // ⌁
    enq=  #$22A0;                       // ⊠
    ack=  #$2713;                       // ✓
    bel=  #$237E;                       // ⍾
    bsW=  #$232B;                       // ⌫
    bsB=  #$2196;                       // ↖ The ECMA glyph is slightly curved
    bs=   bsB;                          //   and has no Unicode representation.
    ht=   #$2AAB;                       // ⪫
    lf=   #$2261;                       // ≡
    vt=   #$2A5B;                       // ⩛
    ff=   #$21A1;                       // ↡
    crW=  #$2aaa;                       // ⪪ ECMA the same
    crB=  #$25bf;                       // ▿
    cr=   crW;
    so=   #$2297;                       // ⊗
    si=   #$2299;                       // ⊙
    dle=  #$229F;                       // ⊟
    dc1=  #$25F7;                       // ◷ Nota bene: these rotate deosil
    dc2=  #$25F6;                       // ◶
    dc3=  #$25F5;                       // ◵
    dc4=  #$25F4;                       // ◴
    nak=  #$237B;                       // ⍻
    syn=  #$238D;                       // ⎍
    etb=  #$22A3;                       // ⊣
    can=  #$29D6;                       // ⧖
    em=   #$237F;                       // ⍿
    sub=  #$2426;                       // ␦
    esc=  #$2296;                       // ⊖
    fs=   #$25F0;                       // ◰ Nota bene: these rotate widdershins
    gsW=  #$25F1;                       // ◱ ECMA the same
    gsB=  #$25b5;                       // ▵
    gs=   gsW;
    rsW=  #$25F2;                       // ◲ ECMA the same
    rsB=  #$25c3;                       // ◃
    rs=   rsW;
    usW=  #$25F3;                       // ◳ ECMA the same
    usB=  #$25b9;                       // ▹
    us=   usW;
    del=  #$2425;                       // ␥
    bar=  #$033c;                       // ̼'

(* Not represented above is a Burroughs glyph for ETX, which in the material    *)
(* available to me appears indistinguisable from CAN. If anybody has variant    *)
(* glyphs from other manufacturers please contribute.                           *)

  var
    i, test, masked: integer;
    changed: boolean;

  begin
    SetLength(result, Length(str));

  (* This should probably be recoded to use a persistent table, but doing it    *)
  (* this way results in no lookup for plain text which is likely to be the     *)
  (* bulk of the output. I'm not making any assumptions about the Unicode       *)
  (* characters being sequential so that this code can be used both for control *)
  (* pictures and ISO-2047 glyphs, and so that if somebody has (good) reason to *)
  (* want to adjust them she can do so.                                         *)

    for i := Length(str) downto 1 do begin
      test := Ord(str[i]);
      if c1Underbar then
        masked := test and $7f          (* Handle both C0 and C1 in one operation *)
      else
        masked := test;
      changed := true;
      case masked of
        $00: result[i] := nul;
        $01: result[i] := soh;
        $02: result[i] := stx;
        $03: result[i] := etx;
        $04: result[i] := eot;
        $05: result[i] := enq;
        $06: result[i] := ack;
        $07: result[i] := bel;
        $08: result[i] := bs;
        $09: result[i] := ht;
        $0a: result[i] := lf;
        $0b: result[i] := vt;
        $0c: result[i] := ff;
        $0d: result[i] := cr;
        $0e: result[i] := so;
        $0f: result[i] := si;
        $10: result[i] := dle;
        $11: result[i] := dc1;
        $12: result[i] := dc2;
        $13: result[i] := dc3;
        $14: result[i] := dc4;
        $15: result[i] := nak;
        $16: result[i] := syn;
        $17: result[i] := etb;
        $18: result[i] := can;
        $19: result[i] := em;
        $1a: result[i] := sub;
        $1b: result[i] := esc;
        $1c: result[i] := fs;
        $1d: result[i] := gs;
        $1e: result[i] := rs;
        $1f: result[i] := us;
        $7f: result[i] := del
      otherwise
        result[i] := Chr(test);
        changed := false;
      end;
      if c1Underbar and changed and     (* Now fix changed C1 characters        *)
                                (masked <> test) then
        Insert(bar, result, i)
    end
  end { withIso2047 } ;


  (* Convert the string that's arrived from GDB etc. into UTF-8. In this case
    it's mostly a dummy operation, except that there might be widget-set-specific
    hacks.
  *)
  function widen(const str: string): widestring;

  const
    dot= #$00B7;                        // ·

  var
    i: integer;

  begin
    SetLength(result, Length(str));
    for i := Length(str) downto 1 do
      case str[i] of
        ' ': result[i] := ' ';          (* Satisfy syntax requirement           *)
        #$00: result[i] := dot;         (* GTK2 really doesn't like seeing this *)
//        #$01..#$0f:   result[i] := dot;
//        #$10..#$1f: result[i] := dot;
//        #$7f:       result[i] := dot;
//        #$80..#$ff: result[i] := dot
      otherwise
        result[i] := str[i]
      end
  end { widen } ;


  (* Look at the line index cl in a TStringList. Assume that at the start there
    will be a line number and padding occupying nl characters, after that will
    be text. Convert the text to hex possibly inserting extra lines after the
    one being processed, only the first (i.e. original) line has a line number.
  *)
  procedure expandAsHex(var stringList: TStringList; currentLine, lineNumberLength: integer);

  var
    lineNumberAsText: string;
    dataAsByteArray: TBytes;
    lengthLastBlock, startLastBlock: integer;


    (* Recursively process the byte array from the end to the beginning. All
      lines are inserted immediately after the original current line, except for
      the final line processed which overwrites the original.
    *)
    procedure hexLines(start, bytes: integer);


      (* The parameter is a line number as text or an equivalent run of spaces.
        The result is a line of hex + ASCII data.
      *)
      function oneHexLine(const lineNum: string): widestring;

      var
        i: integer;

      begin
        result := lineNum;
        for i := 0 to 15 do
          if i < bytes then
            result += LowerCase(HexStr(dataAsByteArray[start + i], 2)) + ' '
          else
            result += '   ';
        result += ' ';                  (* Between hex and ASCII                *)
        for i := 0 to 15 do
          if i < bytes then
            case dataAsByteArray[start + i] of
              $20..$7e: result += Chr(dataAsByteArray[start + i])
            otherwise
              result += #$00B7          // ·
            end
      end { oneHexLine } ;


    begin
      if start = 0 then
        stringList[currentLine] := oneHexLine(lineNumberAsText)
      else begin
        stringList.insert(currentLine + 1, oneHexLine(PadLeft('', Length(lineNumberAsText))));
        hexLines(start - 16, 16)
      end
    end { hexLines } ;


  begin
    if lineNumberLength = 0 then begin
      lineNumberAsText := '';
      dataAsByteArray := BytesOf(Copy(stringList[currentLine], 1,
                                Length(stringList[currentLine])))
    end else begin                      (* Remember one extra space after number *)
      lineNumberAsText := Copy(stringList[currentLine], 1, lineNumberLength + 1);
      dataAsByteArray := BytesOf(Copy(stringList[currentLine], lineNumberLength + 2,
                                Length(stringList[currentLine]) - (lineNumberLength + 1)))
    end;
    if (Length(dataAsByteArray) > 0) and ((Length(dataAsByteArray) mod 16) = 0) then
      lengthLastBlock := 16
    else
      lengthLastBlock := Length(dataAsByteArray) mod 16;
    startLastBlock := Length(dataAsByteArray) - lengthLastBlock;
    hexLines(startLastBlock, lengthLastBlock)
  end { expandAsHex } ;


begin
  if ttyHandle = handleUnopened then begin (* Do this at first output only      *)
    //DebugLn(DBG_VERBOSE, ['TPseudoConsoleDlg.AddOutput Calling consoleSizeChanged']);
    consoleSizeChanged
  end;

(* Get the maximum number of lines to be displayed from the user interface,     *)
(* work out how much space is needed to display a line number, and if necessary *)
(* trim the amount of currently-stored text.                                    *)

  try
    lineLimit := StrToInt(Trim(MaskEdit1.Text))
  except
    MaskEdit1.Text := '5000';
    lineLimit := 5000
  end;
  if CheckGroupRight.Checked[0] then    (* Line numbers?                        *)
    case lineLimit + fFirstLine - 1 of
      0..999:          numLength := 3;
      1000..99999:     numLength := 5;
      100000..9999999: numLength := 7
    otherwise
      numLength := 9
    end
  else
    numLength := 0;
  Memo1.Lines.BeginUpdate;
  while Memo1.Lines.Count > lineLimit do
    Memo1.Lines.Delete(0);

(* Use an intermediate buffer to process the line or potentially lines of text  *)
(* passed as the parameter; where formatting as hex breaks it up into multiple  *)
(* lines, the line number is blanked on the synthetic ones. When lines or lists *)
(* of lines are processed in reverse it is because an indeterminate number of   *)
(* insertions (e.g. Unicode combining diacritics or extended hex output) may be *)
(* inserted after the current index.                                            *)
(*                                                                              *)
(* This might look like a bit of a palaver, but a standard memo might exhibit   *)
(* "interesting" behavior once the amount of text causes it to start scrolling  *)
(* so having an intermediate that can be inspected might be useful.             *)

  buffer := TStringList.Create;
  try
    buffer.Text := AText;     (* Decides what line breaks it wants to swallow   *)
    if buffer.Count = 1 then
      i := 12345              (* Good place for a breakpoint                    *)
    else
      i := 67890;             (* Another good place for a breakpoint            *)
    case RadioGroupRight.ItemIndex of
      0: for i := 0 to buffer.Count - 1 do
           buffer[i] := widen(buffer[i]);
      1: for i := 0 to buffer.Count - 1 do
           buffer[i] := withControlPictures(buffer[i], CheckGroupRight.Checked[1]);
      2: for i := 0 to buffer.Count - 1 do
           buffer[i] := withIso2047(buffer[i], CheckGroupRight.Checked[1])
    otherwise
    end;
    for i := 0 to buffer.Count - 1 do begin             (* Line numbers         *)
      if numLength > 0 then
        buffer[i] := PadLeft(IntToStr(fFirstLine), numLength) + ' ' + buffer[i];
      fFirstLine += 1
    end;
    if RadioGroupRight.ItemIndex = 3 then (* Expand hex line-by-line in reverse *)
      for i := buffer.Count - 1 downto 0 do
        expandAsHex(buffer, i, numLength);

(* Add the buffered text to the visible control(s), and clean up.               *)

    Memo1.Lines.AddStrings(buffer)
  finally
    buffer.Free;
    Memo1.Lines.EndUpdate
  end;
  Memo1.SelStart := length(Memo1.Text)
end { TPseudoConsoleDlg.AddOutput } ;


procedure TPseudoConsoleDlg.Clear;

begin
  //DebugLn(DBG_VERBOSE, ['TPseudoConsoleDlg.Clear Calling FormResize']);
  Memo1.Lines.BeginUpdate;
  try
    FormResize(nil);                    (* Safe during IDE initialisation       *)
    Memo1.Text := ''
  finally
    Memo1.Lines.EndUpdate;
  end;
  fFirstLine := 1
end { TPseudoConsoleDlg.Clear } ;


{$R *.lfm}

initialization
  //DBG_VERBOSE := DebugLogger.FindOrRegisterLogGroup('DBG_VERBOSE' {$IFDEF DBG_VERBOSE} , True {$ENDIF} );
  DBG_WARNINGS := DebugLogger.FindOrRegisterLogGroup('DBG_WARNINGS' {$IFDEF DBG_WARNINGS} , True {$ENDIF} );

  PseudoTerminalDlgWindowCreator := IDEWindowCreators.Add(DebugDialogNames[ddtPseudoTerminal]);
  PseudoTerminalDlgWindowCreator.OnCreateFormProc := @CreateDebugDialog;
  PseudoTerminalDlgWindowCreator.CreateSimpleLayout;

end.