cfad47cfa3/tads3/vmconsol.cpp

4b825dc642cb6eb9a060e54bf8d69288fbee4904cfad47cfa334b206c65f22086bcc5d63e6f70944
1
#ifdef RCSID
2
static char RCSid[] =
3
"$Header$";
4
#endif
5
6
/* 
7
 *   Copyright (c) 1987, 2002 Michael J. Roberts.  All Rights Reserved.
8
 *   
9
 *   Please see the accompanying license file, LICENSE.TXT, for information
10
 *   on using and copying this software.  
11
 */
12
/*
13
Name
14
  vmconsol.cpp - TADS 3 console input reader and output formatter
15
Function
16
  Provides console input and output for the TADS 3 built-in function set
17
  for the T3 VM, including the output formatter.
18
19
  T3 uses the UTF-8 character set to represent character strings.  The OS
20
  functions use the local character set.  We perform the mapping between
21
  UTF-8 and the local character set within this module, so that OS routines
22
  see local characters only, not UTF-8.
23
24
  This code is based on the TADS 2 output formatter, but has been
25
  substantially reworked for C++, Unicode, and the slightly different
26
  TADS 3 formatting model.
27
Notes
28
29
Returns
30
  None
31
Modified
32
  08/25/99 MJRoberts  - created from TADS 2 output formatter
33
*/
34
35
#include <stdio.h>
36
#include <ctype.h>
37
#include <stdlib.h>
38
#include <string.h>
39
#include <stdarg.h>
40
#include "wchar.h"
41
42
#include "os.h"
43
#include "t3std.h"
44
#include "utf8.h"
45
#include "charmap.h"
46
#include "vmuni.h"
47
#include "vmconsol.h"
48
#include "vmglob.h"
49
#include "vmhash.h"
50
51
52
/* ------------------------------------------------------------------------ */
53
/*
54
 *   Log-file formatter subclass implementation 
55
 */
56
57
/*
58
 *   delete 
59
 */
60
CVmFormatterLog::~CVmFormatterLog()
61
{
62
    /* close any active log file */
63
    close_log_file();
64
}
65
66
/*
67
 *   Open a new log file 
68
 */
69
int CVmFormatterLog::open_log_file(const char *fname)
70
{
71
    /* close any existing log file */
72
    if (close_log_file())
73
        return 1;
74
75
    /* reinitialize */
76
    init();
77
78
    /* save the filename for later (we'll need it when we close the file) */
79
    logfname_ = lib_copy_str(fname);
80
81
    /* open the new file */
82
    logfp_ = osfopwt(fname, OSFTLOG);
83
84
    /* return success if we successfully opened the file, failure otherwise */
85
    return (logfp_ == 0);
86
}
87
88
/*
89
 *   Set the log file to a file opened by the caller 
90
 */
91
int CVmFormatterLog::set_log_file(const char *fname, osfildef *fp)
92
{
93
    /* close any existing log file */
94
    if (close_log_file())
95
        return 1;
96
97
    /* reinitialize */
98
    init();
99
100
    /* remember the file */
101
    logfp_ = fp;
102
103
    /* remember the filename */
104
    logfname_ = lib_copy_str(fname);
105
106
    /* success */
107
    return 0;
108
}
109
110
/*
111
 *   Close the log file 
112
 */
113
int CVmFormatterLog::close_log_file()
114
{
115
    /* if we have a file, close it */
116
    if (logfp_ != 0)
117
    {
118
        /* close the handle */
119
        osfcls(logfp_);
120
121
        /* forget about our log file handle */
122
        logfp_ = 0;
123
124
        /* set the system file type to "log file" */
125
        if (logfname_ != 0)
126
            os_settype(logfname_, OSFTLOG);
127
    }
128
129
    /* forget the log file name, if we have one */
130
    if (logfname_ != 0)
131
    {
132
        lib_free_str(logfname_);
133
        logfname_ = 0;
134
    }
135
136
    /* success */
137
    return 0;
138
}
139
140
141
/* ------------------------------------------------------------------------ */
142
/*
143
 *   Base Formatter 
144
 */
145
146
/*
147
 *   deletion
148
 */
149
CVmFormatter::~CVmFormatter()
150
{
151
    /* if we have a table of horizontal tabs, delete it */
152
    if (tabs_ != 0)
153
        delete tabs_;
154
155
    /* forget the character mapper */
156
    set_charmap(0);
157
}
158
159
/*
160
 *   set a new character mapper 
161
 */
162
void CVmFormatter::set_charmap(CCharmapToLocal *cmap)
163
{
164
    /* add a reference to the new mapper, if we have one */
165
    if (cmap != 0)
166
        cmap->add_ref();
167
168
    /* release our reference on any old mapper */
169
    if (cmap_ != 0)
170
        cmap_->release_ref();
171
172
    /* remember the new mapper */
173
    cmap_ = cmap;
174
}
175
176
/*
177
 *   Write out a line.  Text we receive is in the UTF-8 character set.
178
 */
179
void CVmFormatter::write_text(VMG_ const wchar_t *txt, size_t cnt,
180
                              const vmcon_color_t *colors, vm_nl_type nl)
181
{
182
    /* 
183
     *   Check the "script quiet" mode - this indicates that we're reading
184
     *   a script and not echoing output to the display.  If this mode is
185
     *   on, and we're writing to the display, suppress this write.  If
186
     *   the mode is off, or we're writing to a non-display stream (such
187
     *   as a log file stream), show the output as normal.  
188
     */
189
    if (!console_->is_quiet_script() || !is_disp_stream_)
190
    {
191
        char local_buf[128];
192
        char *dst;
193
        size_t rem;
194
195
        /*
196
         *   Check to see if we've reached the end of the screen, and if so
197
         *   run the MORE prompt.  Note that we don't show a MORE prompt
198
         *   unless we're in "formatter more mode," since if we're not, then
199
         *   the OS layer code is taking responsibility for pagination
200
         *   issues.
201
         *   
202
         *   Note that we suppress the MORE prompt if we're showing a
203
         *   continuation of a line already partially shown.  We only want to
204
         *   show a MORE prompt at the start of a new line.
205
         *   
206
         *   Skip the MORE prompt if this stream doesn't use it.  
207
         */
208
        if (formatter_more_mode()
209
            && console_->is_more_mode()
210
            && !is_continuation_
211
            && linecnt_ + 1 >= console_->get_page_length())
212
        {
213
            /* set the standard text color */
214
            set_os_text_color(OS_COLOR_P_TEXT, OS_COLOR_P_TEXTBG);
215
            set_os_text_attr(0);
216
217
            /* display the MORE prompt */
218
            console_->show_more_prompt(vmg0_);
219
220
            /* restore the current color scheme */
221
            set_os_text_color(os_color_.fg, os_color_.bg);
222
            set_os_text_attr(os_color_.attr);
223
        }
224
225
        /* count the line if a newline follows */
226
        if (nl != VM_NL_NONE && nl != VM_NL_NONE_INTERNAL)
227
            ++linecnt_;
228
229
        /* convert and display the text */
230
        for (dst = local_buf, rem = sizeof(local_buf) - 1 ; cnt != 0 ; )
231
        {
232
            size_t cur;
233
            size_t old_rem;
234
            wchar_t c;
235
            
236
            /* 
237
             *   if this character is in a new color, write out the OS-level
238
             *   color switch code 
239
             */
240
            if (colors != 0 && !colors->equals(&os_color_))
241
            {
242
                /* 
243
                 *   null-terminate and display what's in the buffer so far,
244
                 *   so that we close out all of the remaining text in the
245
                 *   old color and attributes
246
                 */
247
                *dst = '\0';
248
                print_to_os(local_buf);
249
250
                /* reset to the start of the local output buffer */
251
                dst = local_buf;
252
                rem = sizeof(local_buf) - 1;
253
254
                /* set the text attributes, if they changed */
255
                if (colors->attr != os_color_.attr)
256
                    set_os_text_attr(colors->attr);
257
258
                /* set the color, if it changed */
259
                if (colors->fg != os_color_.fg
260
                    || colors->bg != os_color_.bg)
261
                    set_os_text_color(colors->fg, colors->bg);
262
263
                /* 
264
                 *   Whatever happened, set our new color internally as the
265
                 *   last color we sent to the OS.  Even if we didn't
266
                 *   actually do anything, we'll at least know we won't have
267
                 *   to do anything more until we find another new color. 
268
                 */
269
                os_color_ = *colors;
270
            }
271
272
            /* get this character */
273
            c = *txt;
274
275
            /* 
276
             *   translate non-breaking spaces into ordinary spaces if the
277
             *   underlying target isn't HTML-based 
278
             */
279
            if (!html_target_ && c == 0x00A0)
280
                c = ' ';
281
282
            /* try storing another character */
283
            old_rem = rem;
284
            cur = (cmap_ != 0 ? cmap_ : G_cmap_to_ui)->map(c, &dst, &rem);
285
286
            /* if that failed, flush the buffer and try again */
287
            if (cur > old_rem)
288
            {
289
                /* null-terminate the buffer */
290
                *dst = '\0';
291
                
292
                /* display the text */
293
                print_to_os(local_buf);
294
295
                /* reset to the start of the local output buffer */
296
                dst = local_buf;
297
                rem = sizeof(local_buf) - 1;
298
            }
299
            else
300
            {
301
                /* we've now consumed this character of input */
302
                ++txt;
303
                --cnt;
304
                if (colors != 0)
305
                    ++colors;
306
            }
307
        }
308
309
        /* if we have a partially-filled buffer, display it */
310
        if (dst > local_buf)
311
        {
312
            /* null-terminate and display the buffer */
313
            *dst = '\0';
314
            print_to_os(local_buf);
315
        }
316
317
        /* write the appropriate type of line termination */
318
        switch(nl)
319
        {
320
        case VM_NL_NONE:
321
        case VM_NL_INPUT:
322
        case VM_NL_NONE_INTERNAL:
323
            /* no line termination is needed */
324
            break;
325
326
        case VM_NL_NEWLINE:
327
            /* write a newline */
328
            print_to_os(html_target_ ? "<BR HEIGHT=0>\n" : "\n");
329
            break;
330
331
        case VM_NL_OSNEWLINE:
332
            /* 
333
             *   the OS will provide a newline, but add a space to make it
334
             *   explicit that we can break the line here 
335
             */
336
            print_to_os(" ");
337
            break;
338
        }
339
    }
340
}
341
342
/* ------------------------------------------------------------------------ */
343
/*
344
 *   Flush the current line to the display, using the given type of line
345
 *   termination.
346
 *   
347
 *   VM_NL_NONE: flush the current line but do not start a new line; more
348
 *   text will follow on the current line.  This is used, for example, to
349
 *   flush text after displaying a prompt and before waiting for user
350
 *   input.
351
 *   
352
 *   VM_NL_INPUT: acts like VM_NL_NONE, except that we flush everything,
353
 *   including trailing spaces.
354
 *   
355
 *   VM_NL_NONE_INTERNAL: same as VM_NL_NONE, but doesn't flush at the OS
356
 *   level.  This is used when we're only flushing our buffers in order to
357
 *   clear out space internally, not because we want the underlying OS
358
 *   renderer to display things immediately.  This distinction is
359
 *   important in HTML mode, since it ensures that the HTML parser only
360
 *   sees well-formed strings when flushing.
361
 *   
362
 *   VM_NL_NEWLINE: flush the line and start a new line by writing out a
363
 *   newline character.
364
 *   
365
 *   VM_NL_OSNEWLINE: flush the line as though starting a new line, but
366
 *   don't add an actual newline character to the output, since the
367
 *   underlying OS display code will handle this.  Instead, add a space
368
 *   after the line to indicate to the OS code that a line break is
369
 *   possible there.  (This differs from VM_NL_NONE in that VM_NL_NONE
370
 *   doesn't add anything at all after the line.)  
371
 */
372
void CVmFormatter::flush(VMG_ vm_nl_type nl)
373
{
374
    int cnt;
375
    vm_nl_type write_nl;
376
377
    /* null-terminate the current output line buffer */
378
    linebuf_[linepos_] = '\0';
379
380
    /* 
381
     *   Expand any pending tab.  Allow "anonymous" tabs only if we're
382
     *   flushing because we're ending the line normally; if we're not
383
     *   ending the line, we can't handle tabs that depend on the line
384
     *   ending. 
385
     */
386
    expand_pending_tab(vmg_ nl == VM_NL_NEWLINE);
387
388
    /* 
389
     *   note number of characters to display - assume we'll display all of
390
     *   the characters in the buffer 
391
     */
392
    cnt = wcslen(linebuf_);
393
394
    /* 
395
     *   Trim trailing spaces, unless we're about to read input or are doing
396
     *   an internal flush.  (Show trailing spaces when reading input, since
397
     *   we won't be able to revise the layout after this point.  Don't trim
398
     *   on an internal flush either, as this kind of flushing simply empties
399
     *   out our buffer exactly as it is.)  
400
     */
401
    if (nl != VM_NL_INPUT && nl != VM_NL_NONE_INTERNAL)
402
    {
403
        /* 
404
         *   look for last non-space character, but keep any spaces that come
405
         *   before an explicit non-breaking flag 
406
         */
407
        for ( ; cnt > 0 && linebuf_[cnt-1] == ' ' ; --cnt)
408
        {
409
            /* don't remove this character if it's marked as non-breaking */
410
            if ((flagbuf_[cnt-1] & VMCON_OBF_NOBREAK) != 0)
411
                break;
412
        }
413
414
        /* 
415
         *   if we're actually doing a newline, discard the trailing spaces
416
         *   for good - we don't want them at the start of the next line 
417
         */
418
        if (nl == VM_NL_NEWLINE)
419
            linepos_ = cnt;
420
    }
421
422
    /* check the newline mode */
423
    switch(nl)
424
    {
425
    case VM_NL_NONE:
426
    case VM_NL_NONE_INTERNAL:
427
        /* no newline - just flush out what we have */
428
        write_nl = VM_NL_NONE;
429
        break;
430
431
    case VM_NL_INPUT:
432
        /* no newline - flush out what we have */
433
        write_nl = VM_NL_NONE;
434
435
        /* on input, reset the HTML parsing state */
436
        html_passthru_state_ = VMCON_HPS_NORMAL;
437
        break;
438
439
    case VM_NL_NEWLINE:
440
        /* 
441
         *   We're adding a newline.  We want to suppress redundant
442
         *   newlines -- we reduce any run of consecutive vertical
443
         *   whitespace to a single newline.  So, if we have anything in
444
         *   this line, or we didn't already just write a newline, write
445
         *   out a newline now; otherwise, write nothing.  
446
         */
447
        if (linecol_ != 0 || !just_did_nl_)
448
        {
449
            /* add the newline */
450
            write_nl = VM_NL_NEWLINE;
451
        }
452
        else
453
        {
454
            /* 
455
             *   Don't write out a newline after all - the line buffer is
456
             *   empty, and we just wrote a newline, so this is a
457
             *   redundant newline that we wish to suppress (so that we
458
             *   collapse a run of vertical whitespace down to a single
459
             *   newline).  
460
             */
461
            write_nl = VM_NL_NONE;
462
        }
463
        break;
464
465
    case VM_NL_OSNEWLINE:
466
        /* 
467
         *   we're going to depend on the underlying OS output layer to do
468
         *   line breaking, so we won't add a newline, but we will add a
469
         *   space, so that the underlying OS layer knows we have a word
470
         *   break here 
471
         */
472
        write_nl = VM_NL_OSNEWLINE;
473
        break;
474
    }
475
476
    /* 
477
     *   display the line, as long as we have something buffered to
478
     *   display; even if we don't, display it if our column is non-zero
479
     *   and we didn't just do a newline, since this must mean that we've
480
     *   flushed a partial line and are just now doing the newline 
481
     */
482
    if (cnt != 0 || (linecol_ != 0 && !just_did_nl_))
483
    {
484
        /* write it out */
485
        write_text(vmg_ linebuf_, cnt, colorbuf_, write_nl);
486
    }
487
488
    /* check the line ending */
489
    switch (nl)
490
    {
491
    case VM_NL_NONE:
492
    case VM_NL_INPUT:
493
        /* we're not displaying a newline, so flush what we have */
494
        flush_to_os();
495
496
        /* 
497
         *   the subsequent buffer will be a continuation of the current
498
         *   text, if we've displayed anything at all here 
499
         */
500
        is_continuation_ = (linecol_ != 0);
501
        break;
502
503
    case VM_NL_NONE_INTERNAL:
504
        /* 
505
         *   internal buffer flush only - subsequent text will be a
506
         *   continuation of the current line, if there's anything on the
507
         *   current line 
508
         */
509
        is_continuation_ = (linecol_ != 0);
510
        break;
511
512
    default:
513
        /* we displayed a newline, so reset the column position */
514
        linecol_ = 0;
515
516
        /* the next buffer starts a new line on the display */
517
        is_continuation_ = FALSE;
518
        break;
519
    }
520
521
    /* 
522
     *   Move any trailing characters we didn't write in this go to the start
523
     *   of the buffer.  
524
     */
525
    if (cnt < linepos_)
526
    {
527
        size_t movecnt;
528
529
        /* calculate how many trailing characters we didn't write */
530
        movecnt = linepos_ - cnt;
531
532
        /* move the characters, colors, and flags */
533
        memmove(linebuf_, linebuf_ + cnt, movecnt * sizeof(linebuf_[0]));
534
        memmove(colorbuf_, colorbuf_ + cnt, movecnt * sizeof(colorbuf_[0]));
535
        memmove(flagbuf_, flagbuf_ + cnt, movecnt * sizeof(flagbuf_[0]));
536
    }
537
538
    /* move the line output position to follow the preserved characters */
539
    linepos_ -= cnt;
540
541
    /* 
542
     *   If we just output a newline, note it.  If we didn't just output a
543
     *   newline, but we did write out anything else, note that we're no
544
     *   longer at the start of a line on the underlying output device.  
545
     */
546
    if (nl == VM_NL_NEWLINE)
547
        just_did_nl_ = TRUE;
548
    else if (cnt != 0)
549
        just_did_nl_ = FALSE;
550
551
    /* 
552
     *   if the current buffering color doesn't match the current osifc-layer
553
     *   color, then we must need to flush just the new color/attribute
554
     *   settings (this can happen when we have changed the attributes in
555
     *   preparation for reading input, since we won't have any actual text
556
     *   to write after the color change) 
557
     */
558
    if (!cur_color_.equals(&os_color_))
559
    {
560
        /* set the text attributes in the OS window, if they changed */
561
        if (cur_color_.attr != os_color_.attr)
562
            set_os_text_attr(cur_color_.attr);
563
564
        /* set the color in the OS window, if it changed */
565
        if (cur_color_.fg != os_color_.fg
566
            || cur_color_.bg != os_color_.bg)
567
            set_os_text_color(cur_color_.fg, cur_color_.bg);
568
569
        /* set the new osifc color */
570
        os_color_ = cur_color_;
571
    }
572
}
573
574
/* ------------------------------------------------------------------------ */
575
/*
576
 *   Clear out our buffers 
577
 */
578
void CVmFormatter::empty_buffers(VMG0_)
579
{
580
    /* reset our buffer pointers */
581
    linepos_ = 0;
582
    linecol_ = 0;
583
    linebuf_[0] = '\0';
584
    just_did_nl_ = FALSE;
585
    is_continuation_ = FALSE;
586
587
    /* there's no pending tab now */
588
    pending_tab_align_ = VMFMT_TAB_NONE;
589
590
    /* start out at the first line */
591
    linecnt_ = 0;
592
593
    /* reset the HTML lexical state */
594
    html_passthru_state_ = VMCON_HPS_NORMAL;
595
}
596
597
/* ------------------------------------------------------------------------ */
598
/*
599
 *   Immediately update the display window 
600
 */
601
void CVmFormatter::update_display(VMG0_)
602
{
603
    /* update the display window at the OS layer */
604
    os_update_display();
605
}
606
607
/* ------------------------------------------------------------------------ */
608
/*
609
 *   Display a blank line to the stream
610
 */
611
void CVmFormatter::write_blank_line(VMG0_)
612
{
613
    /* flush the stream */
614
    flush(vmg_ VM_NL_NEWLINE);
615
616
    /* if generating for an HTML display target, add an HTML line break */
617
    if (html_target_)
618
        write_text(vmg_ L"<BR>", 4, 0, VM_NL_NONE);
619
620
    /* write out a blank line */
621
    write_text(vmg_ L"", 0, 0, VM_NL_NEWLINE);
622
}
623
624
/* ------------------------------------------------------------------------ */
625
/*
626
 *   Generate a tab for a "\t" sequence in the game text, or a <TAB
627
 *   MULTIPLE> or <TAB INDENT> sequence parsed in our mini-parser.
628
 *   
629
 *   Standard (non-HTML) version: we'll generate enough spaces to take us to
630
 *   the next tab stop.
631
 *   
632
 *   HTML version: if we're in native HTML mode, we'll just generate the
633
 *   equivalent HTML; if we're not in HTML mode, we'll generate a hard tab
634
 *   character, which the HTML formatter will interpret as a <TAB
635
 *   MULTIPLE=4>.  
636
 */
637
void CVmFormatter::write_tab(VMG_ int indent, int multiple)
638
{
639
    int maxcol;
640
641
    /* check to see what the underlying system is expecting */
642
    if (html_target_)
643
    {
644
        char buf[40];
645
646
        /* 
647
         *   the underlying system is HTML - generate an appropriate <TAB>
648
         *   sequence to produce the desired effect 
649
         */
650
        sprintf(buf, "<TAB %s=%d>",
651
                indent != 0 ? "INDENT" : "MULTIPLE",
652
                indent != 0 ? indent : multiple);
653
            
654
        /* write it out */
655
        buffer_string(vmg_ buf);
656
    }
657
    else if (multiple != 0)
658
    {
659
        /* get the maximum column */
660
        maxcol = get_buffer_maxcol();
661
662
        /*
663
         *   We don't have an HTML target, and we have a tab to an every-N
664
         *   stop: expand the tab with spaces.  Keep going until we reach
665
         *   the next tab stop of the given multiple.  
666
         */
667
        do
668
        {
669
            /* stop if we've reached the maximum column */
670
            if (linecol_ >= maxcol)
671
                break;
672
673
            /* add another space */
674
            linebuf_[linepos_] = ' ';
675
            flagbuf_[linepos_] = cur_flags_;
676
            colorbuf_[linepos_] = cur_color_;
677
678
            /* advance one character in the buffer */
679
            ++linepos_;
680
681
            /* advance the column counter */
682
            ++linecol_;
683
        } while ((linecol_ + 1) % multiple != 0);
684
    }
685
    else if (indent != 0)
686
    {
687
        /* 
688
         *   We don't have an HTML target, and we just want to add a given
689
         *   number of spaces.  Simply write out the given number of spaces,
690
         *   up to our maximum column limit.  
691
         */
692
        for (maxcol = get_buffer_maxcol() ;
693
             indent != 0 && linecol_ < maxcol ; --indent)
694
        {
695
            /* add another space */
696
            linebuf_[linepos_] = ' ';
697
            flagbuf_[linepos_] = cur_flags_;
698
            colorbuf_[linepos_] = cur_color_;
699
700
            /* advance one character in the buffer and one column */
701
            ++linepos_;
702
            ++linecol_;
703
        }
704
    }
705
}
706
707
708
/* ------------------------------------------------------------------------ */
709
/*
710
 *   Flush a line 
711
 */
712
void CVmFormatter::flush_line(VMG_ int padding)
713
{
714
    /* 
715
     *   check to see if we're using the underlying display layer's line
716
     *   wrapping 
717
     */
718
    if (os_line_wrap_)
719
    {
720
        /*
721
         *   In the HTML version, we don't need the normal *MORE*
722
         *   processing, since the HTML layer will handle that.
723
         *   Furthermore, we don't need to provide actual newline breaks
724
         *   -- that happens after the HTML is parsed, so we don't have
725
         *   enough information here to figure out actual line breaks.
726
         *   So, we'll just flush out our buffer whenever it fills up, and
727
         *   suppress newlines.
728
         *   
729
         *   Similarly, if we have OS-level line wrapping, don't try to
730
         *   figure out where the line breaks go -- just flush our buffer
731
         *   without a trailing newline whenever the buffer is full, and
732
         *   let the OS layer worry about formatting lines and paragraphs.
733
         *   
734
         *   If we're using padding, use newline mode VM_NL_OSNEWLINE.  If
735
         *   we don't want padding (which is the case if we completely
736
         *   fill up the buffer without finding any word breaks), write
737
         *   out in mode VM_NL_NONE, which just flushes the buffer exactly
738
         *   like it is.  
739
         */
740
        flush(vmg_ padding ? VM_NL_OSNEWLINE : VM_NL_NONE_INTERNAL);
741
    }
742
    else
743
    {
744
        /*
745
         *   Normal mode - we process the *MORE* prompt ourselves, and we
746
         *   are responsible for figuring out where the actual line breaks
747
         *   go.  Use flush() to generate an actual newline whenever we
748
         *   flush out our buffer.  
749
         */
750
        flush(vmg_ VM_NL_NEWLINE);
751
    }
752
}
753
754
755
/* ------------------------------------------------------------------------ */
756
/*
757
 *   Write a character to an output stream.  The character is provided to us
758
 *   as a wide Unicode character.  
759
 */
760
void CVmFormatter::buffer_char(VMG_ wchar_t c)
761
{
762
    const wchar_t *exp;
763
    size_t exp_len;
764
765
    /* check for a display expansion */
766
    exp = (cmap_ != 0 ? cmap_ : G_cmap_to_ui)->get_expansion(c, &exp_len);
767
    if (exp != 0)
768
    {
769
        /* write each character of the expansion */
770
        for ( ; exp_len != 0 ; ++exp, --exp_len)
771
            buffer_expchar(vmg_ *exp);
772
    }
773
    else
774
    {
775
        /* there's no expansion - buffer the character as-is */
776
        buffer_expchar(vmg_ c);
777
    }
778
}
779
780
/*
781
 *   Write an expanded character to an output stream.  
782
 */
783
void CVmFormatter::buffer_expchar(VMG_ wchar_t c)
784
{
785
    int i;
786
    int cwid;
787
    unsigned char cflags;
788
    int shy;
789
    int qspace;
790
791
    /* presume the character takes up only one column */
792
    cwid = 1;
793
794
    /* presume we'll use the current flags for the new character */
795
    cflags = cur_flags_;
796
797
    /* assume it's not a quoted space */
798
    qspace = FALSE;
799
800
    /* 
801
     *   Check for some special characters.
802
     *   
803
     *   If we have an underlying HTML renderer, keep track of the HTML
804
     *   lexical state, so we know if we're in a tag or in ordinary text.  We
805
     *   can pass through all of the special line-breaking and spacing
806
     *   characters to the underlying HTML renderer.
807
     *   
808
     *   If our underlying renderer is a plain text renderer, we actually
809
     *   parse the HTML ourselves, so HTML tags will never make it this far -
810
     *   the caller will already have interpreted any HTML tags and removed
811
     *   them from the text stream, passing only the final plain text to us.
812
     *   However, with a plain text renderer, we have to do all of the work
813
     *   of line breaking, so we must look at the special spacing and
814
     *   line-break control characters.  
815
     */
816
    if (html_target_)
817
    {
818
        /* 
819
         *   track the lexical state of the HTML stream going to the
820
         *   underlying renderer 
821
         */
822
        switch (html_passthru_state_)
823
        {
824
        case VMCON_HPS_MARKUP_END:
825
        case VMCON_HPS_NORMAL:
826
            /* check to see if we're starting a markup */
827
            if (c == '&')
828
                html_passthru_state_ = VMCON_HPS_ENTITY_1ST;
829
            else if (c == '<')
830
                html_passthru_state_ = VMCON_HPS_TAG;
831
            else
832
                html_passthru_state_ = VMCON_HPS_NORMAL;
833
            break;
834
835
        case VMCON_HPS_ENTITY_1ST:
836
            /* check to see what kind of entity we have */
837
            if (c == '#')
838
                html_passthru_state_ = VMCON_HPS_ENTITY_NUM_1ST;
839
            else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
840
                html_passthru_state_ = VMCON_HPS_ENTITY_NAME;
841
            else
842
                html_passthru_state_ = VMCON_HPS_NORMAL;
843
            break;
844
845
        case VMCON_HPS_ENTITY_NUM_1ST:
846
            /* check to see what kind of number we have */
847
            if (c == 'x' || c == 'X')
848
                html_passthru_state_ = VMCON_HPS_ENTITY_HEX;
849
            else if (c >= '0' && c <= '9')
850
                html_passthru_state_ = VMCON_HPS_ENTITY_DEC;
851
            else
852
                html_passthru_state_ = VMCON_HPS_NORMAL;
853
            break;
854
855
        case VMCON_HPS_ENTITY_HEX:
856
            /* see if we're done with hex digits */
857
            if (c == ';')
858
                html_passthru_state_ = VMCON_HPS_MARKUP_END;
859
            else if ((c < '0' || c > '9')
860
                     && (c < 'a' || c > 'f')
861
                     && (c < 'A' || c > 'F'))
862
                html_passthru_state_ = VMCON_HPS_NORMAL;
863
            break;
864
865
        case VMCON_HPS_ENTITY_DEC:
866
            /* see if we're done with decimal digits */
867
            if (c == ';')
868
                html_passthru_state_ = VMCON_HPS_MARKUP_END;
869
            else if (c < '0' || c > '9')
870
                html_passthru_state_ = VMCON_HPS_NORMAL;
871
            break;
872
873
        case VMCON_HPS_ENTITY_NAME:
874
            /* see if we're done with alphanumerics */
875
            if (c == ';')
876
                html_passthru_state_ = VMCON_HPS_MARKUP_END;
877
            else if ((c < 'a' || c > 'z')
878
                     && (c < 'A' || c > 'Z')
879
                     && (c < '0' || c > '9'))
880
                html_passthru_state_ = VMCON_HPS_NORMAL;
881
            break;
882
883
        case VMCON_HPS_TAG:
884
            /* see if we're done with the tag, or entering quoted material */
885
            if (c == '>')
886
                html_passthru_state_ = VMCON_HPS_MARKUP_END;
887
            else if (c == '"')
888
                html_passthru_state_ = VMCON_HPS_DQUOTE;
889
            else if (c == '\'')
890
                html_passthru_state_ = VMCON_HPS_SQUOTE;
891
            break;
892
893
        case VMCON_HPS_SQUOTE:
894
            /* see if we're done with the quoted material */
895
            if (c == '\'')
896
                html_passthru_state_ = VMCON_HPS_TAG;
897
            break;
898
899
        case VMCON_HPS_DQUOTE:
900
            /* see if we're done with the quoted material */
901
            if (c == '"')
902
                html_passthru_state_ = VMCON_HPS_TAG;
903
            break;
904
905
        default:
906
            /* ignore other states */
907
            break;
908
        }
909
    }
910
    else
911
    {
912
        /* check for special characters */
913
        switch(c)
914
        {
915
        case 0x00AD:
916
            /*
917
             *   The Unicode "soft hyphen" character.  This indicates a
918
             *   point at which we can insert a hyphen followed by a soft
919
             *   line break, if it's a convenient point to break the line;
920
             *   if we don't choose to break the line here, the soft hyphen
921
             *   is invisible.  
922
             *   
923
             *   Don't buffer anything at all; instead, just flag the
924
             *   preceding character as being a soft hyphenation point, so
925
             *   that we can insert a hyphen there when we get around to
926
             *   breaking the line.  
927
             */
928
            if (linepos_ != 0)
929
                flagbuf_[linepos_ - 1] |= VMCON_OBF_SHY;
930
931
            /* we don't need to buffer anything, so we're done */
932
            return;
933
934
        case 0xFEFF:
935
            /*
936
             *   The Unicode zero-width non-breaking space.  This indicates
937
             *   a point at which we cannot break the line, even if we could
938
             *   normally break here.  Flag the preceding character as a
939
             *   non-breaking point.  Don't buffer anything for this
940
             *   character, as it's not rendered; it merely controls line
941
             *   breaking.  
942
             */
943
            if (linepos_ != 0)
944
                flagbuf_[linepos_ - 1] |= VMCON_OBF_NOBREAK;
945
946
            /* we don't buffer anything, so we're done */
947
            return;
948
949
        case 0x200B:                                    /* zero-width space */
950
        case 0x200a:                                          /* hair space */
951
        case 0x2008:                                   /* punctuation space */
952
            /* 
953
             *   Zero-width space: This indicates an explicitly allowed
954
             *   line-breaking point, but is rendered as invisible.  Flag the
955
             *   preceding character as an OK-to-break point, but don't
956
             *   buffer anything, as the zero-width space isn't rendered.  
957
             *   
958
             *   Hair and punctuation spaces: Treat these very thin spaces as
959
             *   invisible in a fixed-width font.  These are normally used
960
             *   for subtle typographical effects in proportionally-spaced
961
             *   fonts; for example, for separating a right single quote from
962
             *   an immediately following right double quote (as in a
963
             *   quotation within a quotation: I said, "type 'quote'").  When
964
             *   translating to fixed-pitch type, these special spacing
965
             *   effects aren't usually necessary or desirable because of the
966
             *   built-in space in every character cell.
967
             *   
968
             *   These spaces cancel any explicit non-breaking flag that
969
             *   precedes them, since they cause the flag to act on the
970
             *   space's left edge, while leaving the right edge open for
971
             *   breaking.  Since we don't actually take up any buffer space,
972
             *   push our right edge's breakability back to the preceding
973
             *   character.  
974
             */
975
            if (linepos_ != 0)
976
            {
977
                flagbuf_[linepos_ - 1] &= ~VMCON_OBF_NOBREAK;
978
                flagbuf_[linepos_ - 1] |= VMCON_OBF_OKBREAK;
979
            }
980
981
            /* we don't buffer anything, so we're done */
982
            return;
983
984
        case 0x00A0:
985
            /* non-breaking space - buffer it as given */
986
            break;
987
            
988
        case 0x0015:             /* special internal quoted space character */
989
        case 0x2005:                                   /* four-per-em space */
990
        case 0x2006:                                    /* six-per-em space */
991
        case 0x2007:                                        /* figure space */
992
        case 0x2009:                                          /* thin space */
993
            /* 
994
             *   Treat all of these as non-combining spaces, and render them
995
             *   all as single ordinary spaces.  In text mode, we are
996
             *   limited to a monospaced font, so we can't render any
997
             *   differences among these various thinner-than-normal spaces.
998
             */
999
            qspace = TRUE;
1000
            c = ' ';
1001
            break;
1002
1003
        case 0x2002:                                            /* en space */
1004
        case 0x2004:                                  /* three-per-em space */
1005
            /* 
1006
             *   En space, three-per-em space - mark these as non-combining,
1007
             *   and render them as a two ordinary spaces.  In the case of
1008
             *   an en space, we really do want to take up the space of two
1009
             *   ordinary spaces; for a three-per-em space, we want about a
1010
             *   space and a half, but since we're dealing with a monospaced
1011
             *   font, we have to round up to a full two spaces.  
1012
             */
1013
            qspace = TRUE;
1014
            cwid = 2;
1015
            c = ' ';
1016
            break;
1017
            
1018
        case 0x2003:
1019
            /* em space - mark it as non-combining */
1020
            qspace = TRUE;
1021
            
1022
            /* render this as three ordinary spaces */
1023
            cwid = 3;
1024
            c = ' ';
1025
            break;
1026
1027
        default:
1028
            /* 
1029
             *   Translate any whitespace character to a regular space
1030
             *   character.  Note that, once this is done, we don't need to
1031
             *   worry about calling t3_is_space() any more - we can just
1032
             *   check that we have a regular ' ' character.  
1033
             */
1034
            if (t3_is_space(c))
1035
            {
1036
                /* convert it to an ordinary space */
1037
                c = ' ';
1038
                
1039
                /* if we're in obey-whitespace mode, quote this space */
1040
                qspace = obey_whitespace_;
1041
            }
1042
            break;
1043
        }
1044
    }
1045
1046
    /* if it's a quoted space, mark it as such in the buffer flags */
1047
    if (qspace)
1048
        cflags |= VMCON_OBF_QSPACE;
1049
1050
    /* 
1051
     *   Check for the caps/nocaps flags - but only if our HTML lexical state
1052
     *   in the underlying text stream is plain text, because we don't want
1053
     *   to apply these flags to alphabetic characters that are inside tag or
1054
     *   entity text.  
1055
     */
1056
    if (html_passthru_state_ == VMCON_HPS_NORMAL)
1057
    {
1058
        if ((capsflag_ || allcapsflag_) && t3_is_alpha(c))
1059
        {
1060
            /* capsflag is set, so capitalize this character */
1061
            c = t3_to_upper(c);
1062
            
1063
            /* okay, we've capitalized something; clear flag */
1064
            capsflag_ = FALSE;
1065
        }
1066
        else if (nocapsflag_ && t3_is_alpha(c))
1067
        {
1068
            /* nocapsflag is set, so minisculize this character */
1069
            c = t3_to_lower(c);
1070
            
1071
            /* clear the flag now that we've done the job */
1072
            nocapsflag_ = FALSE;
1073
        }
1074
    }
1075
1076
    /*
1077
     *   If this is a space of some kind, we might be able to consolidate it
1078
     *   with a preceding character. 
1079
     */
1080
    if (c == ' ')
1081
    {
1082
        /* ignore ordinary whitespace at the start of a line */
1083
        if (linecol_ == 0 && !qspace)
1084
            return;
1085
1086
        /* 
1087
         *   Consolidate runs of whitespace.  Ordinary whitespace is
1088
         *   subsumed into any type of quoted spaces, but quoted spaces do
1089
         *   not combine.  
1090
         */
1091
        if (linepos_ > 0)
1092
        {
1093
            wchar_t prv;
1094
1095
            /* get the previous character */
1096
            prv = linebuf_[linepos_ - 1];
1097
            
1098
            /* 
1099
             *   if the new character is an ordinary (combining) whitespace
1100
             *   character, subsume it into any preceding space character 
1101
             */
1102
            if (!qspace && prv == ' ')
1103
                return;
1104
1105
            /* 
1106
             *   if the new character is a quoted space, and the preceding
1107
             *   character is a non-quoted space, subsume the preceding
1108
             *   space into the new character 
1109
             */
1110
            if (qspace
1111
                && prv == ' '
1112
                && !(flagbuf_[linepos_ - 1] & VMCON_OBF_QSPACE))
1113
            {
1114
                /* remove the preceding ordinary whitespace */
1115
                --linepos_;
1116
                --linecol_;
1117
            }
1118
        }
1119
    }
1120
1121
    /* if the new character fits in the line, add it */
1122
    if (linecol_ + cwid < get_buffer_maxcol())
1123
    {
1124
        /* buffer this character */
1125
        buffer_rendered(c, cflags, cwid);
1126
1127
        /* we're finished processing the character */
1128
        return;
1129
    }
1130
1131
    /*
1132
     *   The line would overflow if this character were added.
1133
     *   
1134
     *   If we're trying to output any kind of breakable space, just add it
1135
     *   to the line buffer for now; we'll come back later and figure out
1136
     *   where to break upon buffering the next non-space character.  This
1137
     *   ensures that we don't carry trailing space (even trailing en or em
1138
     *   spaces) to the start of the next line if we have an explicit
1139
     *   newline before the next non-space character.  
1140
     */
1141
    if (c == ' ')
1142
    {
1143
        /* 
1144
         *   We're adding a space, so we'll figure out the breaking later,
1145
         *   when we output the next non-space character.  If the preceding
1146
         *   character is any kind of space, don't bother adding the new
1147
         *   one, since any contiguous whitespace at the end of the line has
1148
         *   no effect on the line's appearance.  
1149
         */
1150
        if (linebuf_[linepos_ - 1] == ' ')
1151
        {
1152
            /* 
1153
             *   We're adding a space to a line that already ends in a
1154
             *   space, so we don't really need to add the character.
1155
             *   However, reflect the virtual addition in the output column
1156
             *   position, since the space does affect our column position.
1157
             *   We know that we're adding the new space even though we have
1158
             *   a space preceding, since we wouldn't have gotten this far
1159
             *   if we were going to collapse the space with a run of
1160
             *   whitespace. 
1161
             */
1162
        }
1163
        else
1164
        {
1165
            /* the line doesn't already end in space, so add the space */
1166
            linebuf_[linepos_] = ' ';
1167
            flagbuf_[linepos_] = cflags;
1168
            colorbuf_[linepos_] = cur_color_;
1169
1170
            /* advance one character in the buffer */
1171
            ++linepos_;
1172
        }
1173
1174
        /* 
1175
         *   Adjust the column position for the added space.  Note that we
1176
         *   adjust by the rendered width of the new character even though
1177
         *   we actually added only one character; we only add one character
1178
         *   to the buffer to avoid buffer overflow, but the column position
1179
         *   needs adjustment by the full rendered width.  The fact that the
1180
         *   actual buffer size and rendered width no longer match isn't
1181
         *   important because the difference is entirely in invisible
1182
         *   whitespace at the right end of the line.  
1183
         */
1184
        linecol_ += cwid;
1185
1186
        /* done for now */
1187
        return;
1188
    }
1189
    
1190
    /*
1191
     *   We're adding something other than an ordinary space to the line,
1192
     *   and the new character won't fit, so we must find an appropriate
1193
     *   point to break the line. 
1194
     *   
1195
     *   First, add the new character to the buffer - it could be
1196
     *   significant in how we calculate the break position.  (Note that we
1197
     *   allocate the buffer with space for one extra character after
1198
     *   reaching the maximum line width, so we know we have room for this.)
1199
     */
1200
    linebuf_[linepos_] = c;
1201
    flagbuf_[linepos_] = cur_flags_;
1202
1203
    /* 
1204
     *   if the underlying OS layer is doing the line wrapping, just flush
1205
     *   out the buffer; don't bother trying to do any line wrapping
1206
     *   ourselves, since this work would just be redundant with what the OS
1207
     *   layer has to do anyway 
1208
     */
1209
    if (os_line_wrap_)
1210
    {
1211
        /* flush the line, adding no padding after it */
1212
        flush_line(vmg_ FALSE);
1213
1214
        /* 
1215
         *   we've completely cleared out the line buffer, so reset all of
1216
         *   the line buffer counters 
1217
         */
1218
        linepos_ = 0;
1219
        linecol_ = 0;
1220
        linebuf_[0] = '\0';
1221
        is_continuation_ = FALSE;
1222
1223
        /* we're done */
1224
        goto done_with_wrapping;
1225
    }
1226
1227
    /*
1228
     *   Scan backwards, looking for a break position.  Start at the current
1229
     *   column: we know we can fit everything up to this point on a line on
1230
     *   the underlying display, so this is the rightmost possible position
1231
     *   at which we could break the line.  Keep going until we find a
1232
     *   breaking point or reach the left edge of the line.  
1233
     */
1234
    for (shy = FALSE, i = linepos_ ; i >= 0 ; --i)
1235
    {
1236
        unsigned char f;
1237
        unsigned char prvf;
1238
        
1239
        /* 
1240
         *   There are two break modes: word-break mode and break-anywhere
1241
         *   mode.  The modes are applied to each character, via the buffer
1242
         *   flags.
1243
         *   
1244
         *   In word-break mode, we can break at any ordinary space, at a
1245
         *   soft hyphen, just after a regular hyphen, or at any explicit
1246
         *   ok-to-break point; but we can't break after any character
1247
         *   marked as a no-break point.
1248
         *   
1249
         *   In break-anywhere mode, we can break between any two
1250
         *   characters, except that we can't break after any character
1251
         *   marked as a no-break point.  
1252
         */
1253
1254
        /* get the current character's flags */
1255
        f = flagbuf_[i];
1256
1257
        /* get the preceding character's flags */
1258
        prvf = (i > 0 ? flagbuf_[i-1] : 0);
1259
1260
        /* 
1261
         *   if the preceding character is marked as a no-break point, we
1262
         *   definitely can't break here, so keep looking 
1263
         */
1264
        if ((prvf & VMCON_OBF_NOBREAK) != 0)
1265
            continue;
1266
1267
        /* 
1268
         *   if the preceding character is marked as an explicit ok-to-break
1269
         *   point, we definitely can break here 
1270
         */
1271
        if ((prvf & VMCON_OBF_OKBREAK) != 0)
1272
            break;
1273
1274
        /* 
1275
         *   If the current character is in a run of break-anywhere text,
1276
         *   then we can insert a break just before the current character.
1277
         *   Likewise, if the preceding character is in a run of
1278
         *   break-anywhere text, we can break just after the preceding
1279
         *   character, which is the same as breaking just before the
1280
         *   current character.
1281
         *   
1282
         *   Note that we must test for both cases to properly handle
1283
         *   boundaries between break-anywhere and word-break text.  If
1284
         *   we're switching from word-break to break-anywhere text, the
1285
         *   current character will be marked as break-anywhere, so if we
1286
         *   only tested the previous character, we'd miss this transition.
1287
         *   If we're switching from break-anywhere to word-break text, the
1288
         *   previous character will be marked as break-anywhere, so we'd
1289
         *   miss the fact that we could break right here (rather than
1290
         *   before the previous character) if we didn't test it explicitly.
1291
         */
1292
        if ((f & VMCON_OBF_BREAK_ANY) != 0
1293
            || (i > 0 && (prvf & VMCON_OBF_BREAK_ANY) != 0))
1294
            break;
1295
1296
        /* 
1297
         *   If the preceding character is marked as a soft hyphenation
1298
         *   point, and we're not at the rightmost position, we can break
1299
         *   here with hyphenation.  We can't break with hyphenation at the
1300
         *   last position because hyphenation requires us to actually
1301
         *   insert a hyphen character, and we know that at the last
1302
         *   position we don't have room for inserting another character.  
1303
         */
1304
        if (i > 0 && i < linepos_ && (prvf & VMCON_OBF_SHY) != 0)
1305
        {
1306
            /* note that we're breaking at a soft hyphen */
1307
            shy = TRUE;
1308
1309
            /* we can break here */
1310
            break;
1311
        }
1312
1313
        /* 
1314
         *   we can break to the left of a space (i.e., we can break before
1315
         *   the current character if the current character is a space) 
1316
         */
1317
        if (linebuf_[i] == ' ')
1318
            break;
1319
1320
        /* 
1321
         *   We can also break to the right of a space.  We need to check
1322
         *   for this case separately from checking that the current
1323
         *   charatcer is a space (which breaks to the left of the space),
1324
         *   because we could have a no-break marker on one side of the
1325
         *   space but not on the other side.  
1326
         */
1327
        if (i > 0 && linebuf_[i-1] == ' ')
1328
            break;
1329
1330
        /* 
1331
         *   If we're to the right of a hyphen, we can break here.  However,
1332
         *   don't break in the middle of a set of consecutive hyphens
1333
         *   (i.e., we don't want to break up "--" sequences).
1334
         */
1335
        if (i > 0 && linebuf_[i-1] == '-' && linebuf_[i] != '-')
1336
            break;
1337
    }
1338
    
1339
    /* check to see if we found a good place to break */
1340
    if (i < 0)
1341
    {
1342
        /*
1343
         *   We didn't find a good place to break.  If the underlying
1344
         *   console allows overrunning the line width, simply add the
1345
         *   character, even though it overflows; otherwise, force a break
1346
         *   at the line width, even though it doesn't occur at a natural
1347
         *   breaking point.
1348
         *   
1349
         *   In any case, don't let our buffer fill up beyond its maximum
1350
         *   size.  
1351
         */
1352
        if (!console_->allow_overrun() || linepos_ + 1 >= OS_MAXWIDTH)
1353
        {
1354
            /* 
1355
             *   we didn't find any good place to break, and the console
1356
             *   doesn't allow us to overrun the terminal width - flush the
1357
             *   entire line as-is, breaking arbitrarily in the middle of a
1358
             *   word 
1359
             */
1360
            flush_line(vmg_ FALSE);
1361
            
1362
            /* 
1363
             *   we've completely cleared out the line buffer, so reset all
1364
             *   of the line buffer counters 
1365
             */
1366
            linepos_ = 0;
1367
            linecol_ = 0;
1368
            linebuf_[0] = '\0';
1369
            is_continuation_ = FALSE;
1370
        }
1371
    }
1372
    else
1373
    {
1374
        wchar_t tmpbuf[OS_MAXWIDTH];
1375
        vmcon_color_t tmpcolor[OS_MAXWIDTH];
1376
        unsigned char tmpflags[OS_MAXWIDTH];
1377
        size_t tmpchars;
1378
        int nxti;
1379
1380
        /* null-terminate the line buffer */        
1381
        linebuf_[linepos_] = '\0';
1382
1383
        /* trim off leading spaces on the next line after the break */
1384
        for (nxti = i ; linebuf_[nxti] == ' ' ; ++nxti) ;
1385
1386
        /* 
1387
         *   The next line starts after the break - save a copy.  We actually
1388
         *   have to save a copy of the trailing material outside the buffer,
1389
         *   since we might have to overwrite the trailing part of the buffer
1390
         *   to expand tabs.  
1391
         */
1392
        tmpchars = wcslen(&linebuf_[nxti]);
1393
        memcpy(tmpbuf, &linebuf_[nxti], tmpchars*sizeof(tmpbuf[0]));
1394
        memcpy(tmpcolor, &colorbuf_[nxti], tmpchars*sizeof(tmpcolor[0]));
1395
        memcpy(tmpflags, &flagbuf_[nxti], tmpchars*sizeof(tmpflags[0]));
1396
1397
        /* if we're breaking at a soft hyphen, insert a real hyphen */
1398
        if (shy)
1399
            linebuf_[i++] = '-';
1400
        
1401
        /* trim off trailing spaces */
1402
        for ( ; i > 0 && linebuf_[i-1] == ' ' ; --i)
1403
        {
1404
            /* stop if we've reached a non-breaking point */
1405
            if ((flagbuf_[i-1] & VMCON_OBF_NOBREAK) != 0)
1406
                break;
1407
        }
1408
1409
        /* terminate the buffer after the break point */
1410
        linebuf_[i] = '\0';
1411
        
1412
        /* write out everything up to the break point */
1413
        flush_line(vmg_ TRUE);
1414
1415
        /* move the saved start of the next line into the line buffer */
1416
        memcpy(linebuf_, tmpbuf, tmpchars*sizeof(tmpbuf[0]));
1417
        memcpy(colorbuf_, tmpcolor, tmpchars*sizeof(tmpcolor[0]));
1418
        memcpy(flagbuf_, tmpflags, tmpchars*sizeof(tmpflags[0]));
1419
        linecol_ = linepos_ = tmpchars;
1420
    }
1421
    
1422
done_with_wrapping:
1423
    /* add the new character to buffer */
1424
    buffer_rendered(c, cflags, cwid);
1425
}
1426
1427
/*
1428
 *   Write a rendered character to an output stream buffer.  This is a
1429
 *   low-level internal routine that we call from buffer_expchar() to put
1430
 *   the final rendition of a character into a buffer.
1431
 *   
1432
 *   Some characters render as multiple copies of a single character; 'wid'
1433
 *   gives the number of copies to store.  The caller is responsible for
1434
 *   ensuring that the rendered representation fits in the buffer and in the
1435
 *   available line width.  
1436
 */
1437
void CVmFormatter::buffer_rendered(wchar_t c, unsigned char flags, int wid)
1438
{
1439
    unsigned char flags_before;
1440
1441
    /* note whether or not we have a break before us */
1442
    flags_before = (linepos_ > 0
1443
                    ? flagbuf_[linepos_-1] & VMCON_OBF_NOBREAK
1444
                    : 0);
1445
1446
    /* add the character the given number of times */
1447
    for ( ; wid != 0 ; --wid)
1448
    {
1449
        /* buffer the character */
1450
        linebuf_[linepos_] = c;
1451
        flagbuf_[linepos_] = flags;
1452
        colorbuf_[linepos_] = cur_color_;
1453
1454
        /* 
1455
         *   if this isn't the last part of the character, carry forward any
1456
         *   no-break flag from the previous part of the character; this will
1457
         *   ensure that a no-break to the left of the sequence applies to
1458
         *   the entire sequence 
1459
         */
1460
        if (wid > 1)
1461
            flagbuf_[linepos_] |= flags_before;
1462
1463
        /* advance one character in the buffer */
1464
        ++linepos_;
1465
1466
        /* adjust our column counter */
1467
        ++linecol_;
1468
    }
1469
}
1470
1471
/* ------------------------------------------------------------------------ */
1472
/* 
1473
 *   write out a UTF-8 string
1474
 */
1475
void CVmFormatter::buffer_string(VMG_ const char *txt)
1476
{
1477
    /* write out each character in the string */
1478
    for ( ; utf8_ptr::s_getch(txt) != 0 ; txt += utf8_ptr::s_charsize(*txt))
1479
        buffer_char(vmg_ utf8_ptr::s_getch(txt));
1480
}
1481
1482
/*
1483
 *   write out a wide unicode string 
1484
 */
1485
void CVmFormatter::buffer_wstring(VMG_ const wchar_t *txt)
1486
{
1487
    /* write out each wide character */
1488
    for ( ; *txt != '\0' ; ++txt)
1489
        buffer_char(vmg_ *txt);
1490
}
1491
1492
1493
/* ------------------------------------------------------------------------ */
1494
/*
1495
 *   Get the next wide unicode character in a UTF8-encoded string, and
1496
 *   update the string pointer and remaining length.  Returns zero if no
1497
 *   more characters are available in the string.  
1498
 */
1499
wchar_t CVmFormatter::next_wchar(const char **s, size_t *len)
1500
{
1501
    wchar_t ret;
1502
    size_t charsize;
1503
1504
    /* if there's nothing left, return a null terminator */
1505
    if (*len == 0)
1506
        return 0;
1507
1508
    /* get this character */
1509
    ret = utf8_ptr::s_getch(*s);
1510
1511
    /* advance the string pointer and length counter */
1512
    charsize = utf8_ptr::s_charsize(**s);
1513
    *len -= charsize;
1514
    *s += charsize;
1515
1516
    /* return the result */
1517
    return ret;
1518
}
1519
1520
/* ------------------------------------------------------------------------ */
1521
/*
1522
 *   Display a string of a given length.  The text is encoded as UTF-8
1523
 *   characters.  
1524
 */
1525
int CVmFormatter::format_text(VMG_ const char *s, size_t slen)
1526
{
1527
    wchar_t c;
1528
    int done = FALSE;
1529
1530
    /* get the first character */
1531
    c = next_wchar(&s, &slen);
1532
1533
    /* if we have anything to show, show it */
1534
    while (c != '\0')
1535
    {
1536
        /* 
1537
         *   first, process the character through our built-in text-only HTML
1538
         *   mini-parser, if our HTML mini-parser state indicates that we're
1539
         *   in the midst of parsing a tag 
1540
         */
1541
        if (html_parse_state_ != VMCON_HPS_NORMAL
1542
            || (html_in_ignore_ && c != '&' && c != '<'))
1543
        {
1544
            /* run our HTML parsing until we finish the tag */
1545
            c = resume_html_parsing(vmg_ c, &s, &slen);
1546
1547
            /* proceed with the next character */
1548
            continue;
1549
        }
1550
1551
        /* check for special characters */
1552
        switch(c)
1553
        {
1554
        case 10:
1555
            /* newline */
1556
            flush(vmg_ VM_NL_NEWLINE);
1557
            break;
1558
                    
1559
        case 9:
1560
            /* tab - write an ordinary every-4-columns tab */
1561
            write_tab(vmg_ 0, 4);
1562
            break;
1563
1564
        case 0x000B:
1565
            /* \b - blank line */
1566
            write_blank_line(vmg0_);
1567
            break;
1568
                    
1569
        case 0x000F:
1570
            /* capitalize next character */
1571
            capsflag_ = TRUE;
1572
            nocapsflag_ = FALSE;
1573
            break;
1574
1575
        case 0x000E:
1576
            /* un-capitalize next character */
1577
            nocapsflag_ = TRUE;
1578
            capsflag_ = FALSE;
1579
            break;
1580
1581
        case '<':
1582
        case '&':
1583
            /* HTML markup-start character - process it */
1584
            if (html_target_ || literal_mode_)
1585
            {
1586
                /* 
1587
                 *   The underlying OS renderer interprets HTML mark-up
1588
                 *   sequences, OR we're processing all text literally; in
1589
                 *   either case, we don't need to perform any
1590
                 *   interpretation.  Simply pass through the character as
1591
                 *   though it were any other.  
1592
                 */
1593
                goto normal_char;
1594
            }
1595
            else
1596
            {
1597
                /*
1598
                 *   The underlying target does not accept HTML sequences.
1599
                 *   It appears we're at the start of an "&" entity or a tag
1600
                 *   sequence, so parse it, remove it, and replace it (if
1601
                 *   possible) with a text-only equivalent.  
1602
                 */
1603
                c = parse_html_markup(vmg_ c, &s, &slen);
1604
1605
                /* go back and process the next character */
1606
                continue;
1607
            }
1608
            break;
1609
1610
        case 0x0015:                      /* our own quoted space character */
1611
        case 0x00A0:                                  /* non-breaking space */
1612
        case 0x00AD:                                         /* soft hyphen */
1613
        case 0xFEFF:                       /* non-breaking zero-width space */
1614
        case 0x2002:                                            /* en space */
1615
        case 0x2003:                                            /* em space */
1616
        case 0x2004:                                  /* three-per-em space */
1617
        case 0x2005:                                   /* four-per-em space */
1618
        case 0x2006:                                    /* six-per-em space */
1619
        case 0x2007:                                        /* figure space */
1620
        case 0x2008:                                   /* punctuation space */
1621
        case 0x2009:                                          /* thin space */
1622
        case 0x200a:                                          /* hair space */
1623
        case 0x200b:                                    /* zero-width space */
1624
            /* 
1625
             *   Special Unicode characters.  For HTML targets, write these
1626
             *   as &# sequences - this bypasses character set translation
1627
             *   and ensures that the HTML parser will see them as intended.
1628
             */
1629
            if (html_target_)
1630
            {
1631
                char buf[15];
1632
                char *p;
1633
                
1634
                /* 
1635
                 *   it's an HTML target - render these as &# sequences;
1636
                 *   generate the decimal representation of 'c' (in reverse
1637
                 *   order, hence start with the terminating null byte and
1638
                 *   the semicolon) 
1639
                 */
1640
                p = buf + sizeof(buf) - 1;
1641
                *p-- = '\0';
1642
                *p-- = ';';
1643
1644
                /* generate the decimal representation of 'c' */
1645
                for ( ; c != 0 ; c /= 10)
1646
                    *p-- = (c % 10) + '0';
1647
1648
                /* add the '&#' sequence */
1649
                *p-- = '#';
1650
                *p = '&';
1651
1652
                /* write out the sequence */
1653
                buffer_string(vmg_ p);
1654
            }
1655
            else
1656
            {
1657
                /* for non-HTML targets, treat these as normal */
1658
                goto normal_char;
1659
            }
1660
            break;
1661
1662
        default:
1663
        normal_char:
1664
            /* normal character - write it out */
1665
            buffer_char(vmg_ c);
1666
            break;
1667
        }
1668
1669
        /* move on to the next character, unless we're finished */
1670
        if (done)
1671
            c = '\0';
1672
        else
1673
            c = next_wchar(&s, &slen);
1674
    }
1675
1676
    /* success */
1677
    return 0;
1678
}
1679
1680
/* ------------------------------------------------------------------------ */
1681
/*
1682
 *   Initialize the display object 
1683
 */
1684
CVmConsole::CVmConsole()
1685
{
1686
    /* no script file yet */
1687
    script_sp_ = 0;
1688
1689
    /* no command log file yet */
1690
    command_fp_ = 0;
1691
1692
    /* assume we'll double-space after each period */
1693
    doublespace_ = TRUE;
1694
1695
    /* presume we'll have no log stream */
1696
    log_str_ = 0;
1697
    log_enabled_ = FALSE;
1698
}
1699
1700
/*
1701
 *   Delete the display object 
1702
 */
1703
CVmConsole::~CVmConsole()
1704
{
1705
    /* close any active script file(s) */
1706
    while (script_sp_ != 0)
1707
    {
1708
        /* close this file */
1709
        osfcls(script_sp_->fp);
1710
1711
        /* unlink this stack level */
1712
        script_stack_entry *cur = script_sp_;
1713
        script_sp_ = cur->enc;
1714
1715
        /* delete the entry */
1716
        delete cur;
1717
    }
1718
1719
    /* close any active command log file */
1720
    close_command_log();
1721
1722
    /* delete the log stream if we have one */
1723
    if (log_str_ != 0)
1724
        delete log_str_;
1725
}
1726
1727
/* ------------------------------------------------------------------------ */
1728
/*
1729
 *   Display a string of a given byte length 
1730
 */
1731
int CVmConsole::format_text(VMG_ const char *p, size_t len)
1732
{
1733
    /* display the string */
1734
    disp_str_->format_text(vmg_ p, len);
1735
1736
    /* if there's a log file, write to the log file as well */
1737
    if (log_enabled_)
1738
        log_str_->format_text(vmg_ p, len);
1739
1740
    /* indicate success */
1741
    return 0;
1742
}
1743
1744
/*
1745
 *   Display a string on the log stream only 
1746
 */
1747
int CVmConsole::format_text_to_log(VMG_ const char *p, size_t len)
1748
{
1749
    /* if there's a log file, write to it; otherwise ignore the whole thing */
1750
    if (log_enabled_)
1751
        log_str_->format_text(vmg_ p, len);
1752
1753
    /* indicate success */
1754
    return 0;
1755
}
1756
1757
/* ------------------------------------------------------------------------ */
1758
/*
1759
 *   Set the text color 
1760
 */
1761
void CVmConsole::set_text_color(VMG_ os_color_t fg, os_color_t bg)
1762
{
1763
    /* set the color in our main display stream */
1764
    disp_str_->set_text_color(vmg_ fg, bg);
1765
}
1766
1767
/*
1768
 *   Set the body color 
1769
 */
1770
void CVmConsole::set_body_color(VMG_ os_color_t color)
1771
{
1772
    /* set the color in the main display stream */
1773
    disp_str_->set_os_body_color(color);
1774
}
1775
1776
/* ------------------------------------------------------------------------ */
1777
/*
1778
 *   Display a blank line 
1779
 */
1780
void CVmConsole::write_blank_line(VMG0_)
1781
{
1782
    /* generate the newline to the standard display */
1783
    disp_str_->write_blank_line(vmg0_);
1784
1785
    /* if we're logging, generate the newline to the log file as well */
1786
    if (log_enabled_)
1787
        log_str_->write_blank_line(vmg0_);
1788
}
1789
1790
1791
/* ------------------------------------------------------------------------ */
1792
/*
1793
 *   outcaps() - sets an internal flag which makes the next letter output
1794
 *   a capital, whether it came in that way or not.  Set the same state in
1795
 *   both formatters (standard and log).  
1796
 */
1797
void CVmConsole::caps()
1798
{
1799
    disp_str_->caps();
1800
    if (log_enabled_)
1801
        log_str_->caps();
1802
}
1803
1804
/*
1805
 *   outnocaps() - sets the next letter to a miniscule, whether it came in
1806
 *   that way or not.  
1807
 */
1808
void CVmConsole::nocaps()
1809
{
1810
    disp_str_->nocaps();
1811
    if (log_enabled_)
1812
        log_str_->nocaps();
1813
}
1814
1815
/*
1816
 *   obey_whitespace() - sets the obey-whitespace mode 
1817
 */
1818
int CVmConsole::set_obey_whitespace(int f)
1819
{
1820
    int ret;
1821
1822
    /* note the original display stream status */
1823
    ret = disp_str_->get_obey_whitespace();
1824
1825
    /* set the stream status */
1826
    disp_str_->set_obey_whitespace(f);
1827
    if (log_enabled_)
1828
        log_str_->set_obey_whitespace(f);
1829
1830
    /* return the original status of the display stream */
1831
    return ret;
1832
}
1833
1834
/* ------------------------------------------------------------------------ */
1835
/*
1836
 *   Open a log file 
1837
 */
1838
int CVmConsole::open_log_file(const char *fname)
1839
{
1840
    /* if there's no log stream, we can't open a log file */
1841
    if (log_str_ == 0)
1842
        return 1;
1843
1844
    /* 
1845
     *   Tell the log stream to open the file.  Set the log file's HTML
1846
     *   source mode flag to the same value as is currently being used in
1847
     *   the main display stream, so that it will interpret source markups
1848
     *   the same way that the display stream is going to.  
1849
     */
1850
    return log_str_->open_log_file(fname);
1851
}
1852
1853
/*
1854
 *   Close the log file 
1855
 */
1856
int CVmConsole::close_log_file()
1857
{
1858
    /* if there's no log stream, there's obviously no file open */
1859
    if (log_str_ == 0)
1860
        return 1;
1861
1862
    /* tell the log stream to close its file */
1863
    return log_str_->close_log_file();
1864
}
1865
1866
#if 0 //$$$
1867
/*
1868
 *   This code is currently unused.  However, I'm leaving it in for now -
1869
 *   the algorithm takes a little thought, so it would be nicer to be able
1870
 *   to uncomment the existing code should we ever need it in the future.  
1871
 */
1872
1873
/* ------------------------------------------------------------------------ */
1874
/*
1875
 *   Write UTF-8 text explicitly to the log file.  This can be used to add
1876
 *   special text (such as prompt text) that would normally be suppressed
1877
 *   from the log file.  When more mode is turned off, we don't
1878
 *   automatically copy text to the log file; any text that the caller
1879
 *   knows should be in the log file during times when more mode is turned
1880
 *   off can be explicitly added with this function.
1881
 *   
1882
 *   If nl is true, we'll add a newline at the end of this text.  The
1883
 *   caller should not include any newlines in the text being displayed
1884
 *   here.  
1885
 */
1886
void CVmConsole::write_to_logfile(VMG_ const char *txt, int nl)
1887
{
1888
    /* if there's no log file, there's nothing to do */
1889
    if (logfp_ == 0)
1890
        return;
1891
1892
    /* write the text in the log file character set */
1893
    write_to_file(logfp_, txt, G_cmap_to_log);
1894
1895
    /* add a newline if desired */
1896
    if (nl)
1897
    {
1898
        /* add a normal newline */
1899
        os_fprintz(logfp_, "\n");
1900
1901
        /* if the logfile is an html target, write an HTML line break */
1902
        if (log_str_ != 0 && log_str_->is_html_target())
1903
            os_fprintz(logfp_, "<BR HEIGHT=0>\n");
1904
    }
1905
1906
    /* flush the output */
1907
    osfflush(logfp_);
1908
}
1909
1910
/*
1911
 *   Write text to a file in the given character set 
1912
 */
1913
void CVmConsole::write_to_file(osfildef *fp, const char *txt,
1914
                               CCharmapToLocal *map)
1915
{
1916
    size_t txtlen = strlen(txt);
1917
    
1918
    /* 
1919
     *   convert the text from UTF-8 to the local character set and write the
1920
     *   converted text to the log file 
1921
     */
1922
    while (txtlen != 0)
1923
    {
1924
        char local_buf[128];
1925
        size_t src_bytes_used;
1926
        size_t out_bytes;
1927
        
1928
        /* convert as much as we can (leaving room for a null terminator) */
1929
        out_bytes = map->map_utf8(local_buf, sizeof(local_buf),
1930
                                  txt, txtlen, &src_bytes_used);
1931
1932
        /* null-terminate the result */
1933
        local_buf[out_bytes] = '\0';
1934
1935
        /* write the converted text */
1936
        os_fprintz(fp, local_buf);
1937
1938
        /* skip the text we were able to convert */
1939
        txt += src_bytes_used;
1940
        txtlen -= src_bytes_used;
1941
    }
1942
}
1943
#endif /* 0 */
1944
1945
1946
/* ------------------------------------------------------------------------ */
1947
/*
1948
 *   Reset the MORE line counter.  This should be called whenever user
1949
 *   input is read, since stopping to read user input makes it unnecessary
1950
 *   to show another MORE prompt until the point at which input was
1951
 *   solicited scrolls off the screen.  
1952
 */
1953
void CVmConsole::reset_line_count(int clearing)
1954
{
1955
    /* reset the MORE counter in the display stream */
1956
    disp_str_->reset_line_count(clearing);
1957
}
1958
1959
/* ------------------------------------------------------------------------ */
1960
/*
1961
 *   Flush the output line.  We'll write to both the standard display and
1962
 *   the log file, as needed.  
1963
 */
1964
void CVmConsole::flush(VMG_ vm_nl_type nl)
1965
{
1966
    /* flush the display stream */
1967
    disp_str_->flush(vmg_ nl);
1968
1969
    /* flush the log stream, if we have an open log file */
1970
    if (log_enabled_)
1971
        log_str_->flush(vmg_ nl);
1972
}
1973
1974
/* ------------------------------------------------------------------------ */
1975
/*
1976
 *   Clear our buffers
1977
 */
1978
void CVmConsole::empty_buffers(VMG0_)
1979
{
1980
    /* tell the formatter to clear its buffer */
1981
    disp_str_->empty_buffers(vmg0_);
1982
1983
    /* same with the log stream, if applicable */
1984
    if (log_enabled_)
1985
        log_str_->empty_buffers(vmg0_);
1986
}
1987
1988
/* ------------------------------------------------------------------------ */
1989
/*
1990
 *   Immediately update the display 
1991
 */
1992
void CVmConsole::update_display(VMG0_)
1993
{
1994
    /* update the display for the main display stream */
1995
    disp_str_->update_display(vmg0_);
1996
}
1997
1998
/* ------------------------------------------------------------------------ */
1999
/*
2000
 *   Open a script file 
2001
 */
2002
void CVmConsole::open_script_file(const char *fname, int quiet,
2003
                                  int script_more_mode)
2004
{
2005
    int evt;
2006
    char buf[50];
2007
    
2008
    /* try opening the file */
2009
    osfildef *fp = osfoprt(fname, OSFTCMD);
2010
2011
    /* if that failed, silently ignore the request */
2012
    if (fp == 0)
2013
        return;
2014
2015
    /* read the first line to see if it looks like an event script */
2016
    if (osfgets(buf, sizeof(buf), fp) != 0
2017
        && strcmp(buf, "<eventscript>\n") == 0)
2018
    {
2019
        /* remember that it's an event script */
2020
        evt = TRUE;
2021
    }
2022
    else
2023
    {
2024
        /* 
2025
         *   it's not an event script, so it must be a regular command-line
2026
         *   script - rewind it so we read the first line again as a regular
2027
         *   input line 
2028
         */
2029
        evt = FALSE;
2030
        osfseek(fp, 0, OSFSK_SET);
2031
    }
2032
2033
    /* if there's an enclosing script, inherit its modes */
2034
    if (script_sp_ != 0)
2035
    {
2036
        /* 
2037
         *   if the enclosing script is quiet, force the nested script to be
2038
         *   quiet as well 
2039
         */
2040
        if (script_sp_->quiet)
2041
            quiet = TRUE;
2042
2043
        /* 
2044
         *   if the enclosing script is nonstop, force the nested script to
2045
         *   be nonstop as well
2046
         */
2047
        if (!script_sp_->more_mode)
2048
            script_more_mode = FALSE;
2049
    }
2050
2051
    /* push the new script file onto the stack */
2052
    script_sp_ = new script_stack_entry(
2053
        script_sp_, set_more_state(script_more_mode), fp,
2054
        script_more_mode, quiet, evt);
2055
    
2056
    /* turn on NONSTOP mode in the OS layer if applicable */
2057
    if (!script_more_mode)
2058
        os_nonstop_mode(TRUE);
2059
}
2060
2061
/*
2062
 *   Close the current script file 
2063
 */
2064
int CVmConsole::close_script_file()
2065
{
2066
    script_stack_entry *e;
2067
    
2068
    /* if we have a file, close it */
2069
    if ((e = script_sp_) != 0)
2070
    {
2071
        int ret;
2072
        
2073
        /* close the file */
2074
        osfcls(e->fp);
2075
2076
        /* pop the stack */
2077
        script_sp_ = e->enc;
2078
2079
        /* restore the enclosing level's MORE mode */
2080
        os_nonstop_mode(!e->old_more_mode);
2081
2082
        /* 
2083
         *   return the MORE mode in effect before we started reading the
2084
         *   script file 
2085
         */
2086
        ret = e->old_more_mode;
2087
2088
        /* delete the stack level */
2089
        delete e;
2090
2091
        /* return the result */
2092
        return ret;
2093
    }
2094
    else
2095
    {
2096
        /* 
2097
         *   there's no script file - just return the current MORE mode,
2098
         *   since we're not making any changes 
2099
         */
2100
        return is_more_mode();
2101
    }
2102
}
2103
2104
/* ------------------------------------------------------------------------ */
2105
/*
2106
 *   Open a command log file 
2107
 */
2108
int CVmConsole::open_command_log(const char *fname, int event_script)
2109
{
2110
    /* close any existing command log file */
2111
    close_command_log();
2112
    
2113
    /* remember the filename */
2114
    strcpy(command_fname_, fname);
2115
2116
    /* open the file */
2117
    command_fp_ = osfopwt(fname, OSFTCMD);
2118
2119
    /* note the type */
2120
    command_eventscript_ = event_script;
2121
2122
    /* if it's an event script, write the file type tag */
2123
    if (event_script && command_fp_ != 0)
2124
    {
2125
        os_fprintz(command_fp_, "<eventscript>\n");
2126
        osfflush(command_fp_);
2127
    }
2128
2129
    /* return success if we successfully opened the file */
2130
    return (command_fp_ == 0);
2131
}
2132
2133
/* 
2134
 *   close the active command log file 
2135
 */
2136
int CVmConsole::close_command_log()
2137
{
2138
    /* if there's a command log file, close it */
2139
    if (command_fp_ != 0)
2140
    {
2141
        /* close the file */
2142
        osfcls(command_fp_);
2143
2144
        /* set its file type */
2145
        os_settype(command_fname_, OSFTCMD);
2146
2147
        /* forget the file */
2148
        command_fp_ = 0;
2149
    }
2150
2151
    /* success */
2152
    return 0;
2153
}
2154
2155
2156
/* ------------------------------------------------------------------------ */
2157
/*
2158
 *   Read a line of input from the console.  Fills in the buffer with a
2159
 *   null-terminated string in the UTF-8 character set.  Returns zero on
2160
 *   success, non-zero on end-of-file.  
2161
 */
2162
int CVmConsole::read_line(VMG_ char *buf, size_t buflen)
2163
{
2164
    /* cancel any previous interrupted input */
2165
    read_line_cancel(vmg_ TRUE);
2166
2167
try_again:
2168
    /* use the timeout version, with no timeout specified */
2169
    switch(read_line_timeout(vmg_ buf, buflen, 0, FALSE))
2170
    {
2171
    case OS_EVT_LINE:
2172
        /* success */
2173
        return 0;
2174
2175
    case VMCON_EVT_END_SCRIPT:
2176
        /* 
2177
         *   end of script - we have no way to communicate this result back
2178
         *   to our caller, so simply ignore the result and ask for another
2179
         *   line 
2180
         */
2181
        goto try_again;
2182
2183
    default:
2184
        /* anything else is an error */
2185
        return 1;
2186
    }
2187
}
2188
2189
2190
/* ------------------------------------------------------------------------ */
2191
/*
2192
 *   Log an event to the output script.  The parameter is in the UI character
2193
 *   set.  
2194
 */
2195
int CVmConsole::log_event(VMG_ int evt,
2196
                          const char *param, size_t paramlen,
2197
                          int param_is_utf8)
2198
{
2199
    /* if there's a script file, log the event */
2200
    if (command_fp_ != 0)
2201
    {
2202
        /* write the event in the proper format for the script type */
2203
        if (command_eventscript_)
2204
        {
2205
            const char *tag = 0;
2206
            
2207
            /* write the event according to its type */
2208
            switch (evt)
2209
            {
2210
            case OS_EVT_KEY:
2211
                /* use the "<key>" tag */
2212
                tag = "<key>";
2213
                
2214
                /* 
2215
                 *   use the normal key representation, except we want to
2216
                 *   write \n as [enter] and \t as [tab] 
2217
                 */
2218
                if (param != 0)
2219
                {
2220
                    switch (*param)
2221
                    {
2222
                    case '\n':
2223
                        param = "[enter]";
2224
                        paramlen = 7;
2225
                        break;
2226
2227
                    case '\t':
2228
                        param = "[tab]";
2229
                        paramlen = 5;
2230
                        break;
2231
2232
                    case ' ':
2233
                        param = "[space]";
2234
                        paramlen = 7;
2235
                        break;
2236
                    }
2237
                }
2238
                break;
2239
                
2240
            case OS_EVT_TIMEOUT:
2241
                tag = "<timeout>";
2242
                break;
2243
                
2244
            case OS_EVT_HREF:
2245
                tag = "<href>";
2246
                break;
2247
                
2248
            case OS_EVT_NOTIMEOUT:
2249
                tag = "<notimeout>";
2250
                param = 0;
2251
                break;
2252
                
2253
            case OS_EVT_EOF:
2254
                tag = "<eof>";
2255
                param = 0;
2256
                break;
2257
                
2258
            case OS_EVT_LINE:
2259
                tag = "<line>";
2260
                break;
2261
                
2262
            case OS_EVT_COMMAND:
2263
                tag = "<command>";
2264
                break;
2265
2266
            case VMCON_EVT_END_SCRIPT:
2267
                tag = "<endqs>";
2268
                break;
2269
2270
            case VMCON_EVT_DIALOG:
2271
                tag = "<dialog>";
2272
                break;
2273
2274
            case VMCON_EVT_FILE:
2275
                tag = "<file>";
2276
                break;
2277
            }
2278
            
2279
            /* if we found a tag, write it */
2280
            if (tag != 0)
2281
            {
2282
                /* write the tag, in the local character set */
2283
                G_cmap_to_ui->write_file(command_fp_, tag, strlen(tag));
2284
                
2285
                /* add the parameter, if present */
2286
                if (param != 0)
2287
                {
2288
                    if (param_is_utf8)
2289
                        G_cmap_to_ui->write_file(
2290
                            command_fp_, param, paramlen);
2291
                    else
2292
                        os_fprint(command_fp_, param, paramlen);
2293
                }
2294
2295
                /* add the newline */
2296
                G_cmap_to_ui->write_file(command_fp_, "\n", 1);
2297
2298
                /* flush the output */
2299
                osfflush(command_fp_);
2300
            }
2301
        }
2302
        else
2303
        {
2304
            /*
2305
             *   It's a plain old command-line script.  If the event is an
2306
             *   input-line event, record it; otherwise leave it out, as this
2307
             *   script file format can't represent any other event types.  
2308
             */
2309
            if (evt == OS_EVT_LINE && param != 0)
2310
            {
2311
                /* write the ">" prefix */
2312
                G_cmap_to_ui->write_file(command_fp_, ">", 1);
2313
2314
                /* add the command line */
2315
                if (param_is_utf8)
2316
                    G_cmap_to_ui->write_file(command_fp_, param, paramlen);
2317
                else
2318
                    os_fprint(command_fp_, param, paramlen);
2319
2320
                /* add the newline */
2321
                G_cmap_to_ui->write_file(command_fp_, "\n", 1);
2322
2323
                /* flush the output */
2324
                osfflush(command_fp_);
2325
            }
2326
        }
2327
    }
2328
2329
    /* return the event code */
2330
    return evt;
2331
}
2332
2333
/* ------------------------------------------------------------------------ */
2334
/*
2335
 *   Static variables for input state.  We keep these statically, because we
2336
 *   might need to use the values across a series of read_line_timeout calls
2337
 *   if timeouts occur. 
2338
 */
2339
2340
/* original 'more' mode, before input began */
2341
static int S_old_more_mode;
2342
2343
/* flag: input is pending from an interrupted read_line_timeout invocation */
2344
static int S_read_in_progress;
2345
2346
/* local buffer for reading input lines */
2347
static char S_read_buf[256];
2348
2349
2350
/*
2351
 *   Read a line of input from the console, with an optional timeout value. 
2352
 */
2353
int CVmConsole::read_line_timeout(VMG_ char *buf, size_t buflen,
2354
                                  unsigned long timeout, int use_timeout)
2355
{
2356
    int echo_text;
2357
    char *outp;
2358
    size_t outlen;
2359
    int evt;
2360
    int resuming;
2361
2362
    /* 
2363
     *   presume we won't echo the text to the display; in most cases, it
2364
     *   will be echoed to the display in the course of reading it from
2365
     *   the keyboard 
2366
     */
2367
    echo_text = FALSE;
2368
2369
    /* remember the initial MORE mode */
2370
    S_old_more_mode = is_more_mode();
2371
2372
    /*
2373
     *   If we're not resuming an interrupted read already in progress,
2374
     *   initialize some display settings. 
2375
     */
2376
    if (!S_read_in_progress)
2377
    {
2378
        /* 
2379
         *   Turn off MORE mode if it's on - we don't want a MORE prompt
2380
         *   showing up in the midst of user input.  
2381
         */
2382
        S_old_more_mode = set_more_state(FALSE);
2383
2384
        /* 
2385
         *   flush the output; don't start a new line, since we might have
2386
         *   displayed a prompt that is to be on the same line with the user
2387
         *   input 
2388
         */
2389
        flush_all(vmg_ VM_NL_INPUT);
2390
2391
        /* if there's a script file, read from it */
2392
        if (script_sp_ != 0)
2393
        {
2394
        read_script:
2395
            /* note whether we're in quiet mode */
2396
            int was_quiet = script_sp_->quiet;
2397
            
2398
            /* try reading a line from the script file */
2399
            if (read_line_from_script(S_read_buf, sizeof(S_read_buf), &evt))
2400
            {
2401
                /* 
2402
                 *   we successfully got a line from the script file - if
2403
                 *   we're not in quiet mode, make a note to echo the text to
2404
                 *   the display 
2405
                 */
2406
                if (!script_sp_->quiet)
2407
                    echo_text = TRUE;
2408
            }
2409
            else
2410
            {
2411
                int is_quiet;
2412
                
2413
                /* 
2414
                 *   End of script file - return to reading from the
2415
                 *   enclosing level (i.e., the enclosing script, or the
2416
                 *   keyboard if this is the outermost script).  The return
2417
                 *   value from close_script_file() is the MORE mode that was
2418
                 *   in effect before we started reading the script file;
2419
                 *   we'll use this when we restore the enclosing MORE mode
2420
                 *   so that we restore the pre-script MORE mode when we
2421
                 *   return.  
2422
                 */
2423
                S_old_more_mode = close_script_file();
2424
2425
                /* note the new 'quiet' mode */
2426
                is_quiet = (script_sp_ != 0 && script_sp_->quiet);
2427
2428
                /* 
2429
                 *   if we're still reading from a script (which means we
2430
                 *   closed the old script and popped out to an enclosing
2431
                 *   script), and the 'quiet' mode hasn't changed, simply go
2432
                 *   back for another read 
2433
                 */
2434
                if (script_sp_ != 0 && is_quiet == was_quiet)
2435
                    goto read_script;
2436
                
2437
                /* 
2438
                 *   temporarily turn off MORE mode, in case we read from the
2439
                 *   keyboard 
2440
                 */
2441
                set_more_state(FALSE);
2442
                
2443
                /* flush any output we generated while reading the script */
2444
                flush(vmg_ VM_NL_NONE);
2445
                
2446
                /* 
2447
                 *   If we were in quiet mode but no longer are, let the
2448
                 *   caller know we've finished reading a script, so that the
2449
                 *   caller can set up the display properly for reading from
2450
                 *   the keyboard.
2451
                 *   
2452
                 *   If we weren't in quiet mode, we'll simply proceed to the
2453
                 *   normal keyboard reading; when not in quiet mode, no
2454
                 *   special display fixup is needed.  
2455
                 */
2456
                if (was_quiet && !is_quiet)
2457
                {
2458
                    /* return to the old MORE mode */
2459
                    set_more_state(S_old_more_mode);
2460
2461
                    /* add a blank line to the log file, if necessary */
2462
                    if (log_enabled_)
2463
                        log_str_->print_to_os("\n");
2464
2465
                    /* note in the streams that we've read an input line */
2466
                    disp_str_->note_input_line();
2467
                    if (log_str_ != 0)
2468
                        log_str_->note_input_line();
2469
2470
                    /* 
2471
                     *   generate a synthetic "end of script" event to let
2472
                     *   the caller know we're switching back to regular
2473
                     *   keyboard reading 
2474
                     */
2475
                    return log_event(vmg_ VMCON_EVT_END_SCRIPT);
2476
                }
2477
2478
                /*
2479
                 *   Note that we do not have an event yet - we've merely
2480
                 *   closed the script file, and now we're going to continue
2481
                 *   by reading a line from the keyboard instead.  The call
2482
                 *   to close_script_file() above will have left script_sp_
2483
                 *   == 0, so we'll shortly read an event from the keyboard.
2484
                 *   Thus 'evt' is still not set to any value, because we do
2485
                 *   not yet have an event - this is intentional.  
2486
                 */
2487
            }
2488
        }
2489
2490
        /* 
2491
         *   if we're not reading from a scripot, reset the MORE line
2492
         *   counter, since we're reading user input at the current point and
2493
         *   shouldn't pause for a MORE prompt until the text we're reading
2494
         *   has scrolled off the screen 
2495
         */
2496
        if (script_sp_ == 0)
2497
            reset_line_count(FALSE);
2498
    }
2499
2500
    /* 
2501
     *   if reading was already in progress, we're resuming a previously
2502
     *   interrupted read operation 
2503
     */
2504
    resuming = S_read_in_progress;
2505
2506
    /* reading is now in progress */
2507
    S_read_in_progress = TRUE;
2508
2509
    /* 
2510
     *   if we don't have a script file, or we're resuming an interrupted
2511
     *   read operation, read from the keyboard 
2512
     */
2513
    if (script_sp_ == 0 || resuming)
2514
    {
2515
        /* read a line from the keyboard */
2516
        evt = os_gets_timeout((uchar *)S_read_buf, sizeof(S_read_buf),
2517
                              timeout, use_timeout);
2518
2519
        /*
2520
         *   If that failed because timeout is not supported on this
2521
         *   platform, and the caller didn't actually want to use a timeout,
2522
         *   try again with an ordinary os_gets().  If they wanted to use a
2523
         *   timeout, simply return the NOTIMEOUT indication to our caller.  
2524
         */
2525
        if (evt == OS_EVT_NOTIMEOUT && !use_timeout)
2526
        {
2527
            /* perform an ordinary untimed input */
2528
            if (os_gets((uchar *)S_read_buf, sizeof(S_read_buf)) != 0)
2529
            {
2530
                /* success */
2531
                evt = OS_EVT_LINE;
2532
            }
2533
            else
2534
            {
2535
                /* error reading input */
2536
                evt = OS_EVT_EOF;
2537
            }
2538
        }
2539
2540
        /* 
2541
         *   If we actually read a line, notify the display stream that we
2542
         *   read text from the console - it might need to make some
2543
         *   internal bookkeeping adjustments to account for the fact that
2544
         *   we moved the write position around on the display.
2545
         *   
2546
         *   Don't note the input if we timed out, since we haven't finished
2547
         *   reading the line yet in this case.  
2548
         */
2549
        if (evt == OS_EVT_LINE)
2550
        {
2551
            disp_str_->note_input_line();
2552
            if (log_str_ != 0)
2553
                log_str_->note_input_line();
2554
        }
2555
    }
2556
2557
    /* if we got an error, return it */
2558
    if (evt == OS_EVT_EOF)
2559
    {
2560
        set_more_state(S_old_more_mode);
2561
        return log_event(vmg_ evt);
2562
    }
2563
2564
    /* 
2565
     *   Convert the text from the local UI character set to UTF-8.  Reserve
2566
     *   space in the output buffer for the null terminator.  
2567
     */
2568
    outp = buf;
2569
    outlen = buflen - 1;
2570
    G_cmap_from_ui->map(&outp, &outlen, S_read_buf, strlen(S_read_buf));
2571
2572
    /* add the null terminator */
2573
    *outp = '\0';
2574
2575
    /* 
2576
     *   If we need to echo the text (because we read it from a script file),
2577
     *   do so now.  
2578
     */
2579
    if (echo_text)
2580
    {
2581
        /* show the text */
2582
        format_text(vmg_ buf);
2583
2584
        /* add a newline */
2585
        format_text(vmg_ "\n");
2586
    }
2587
2588
    /* if we finished reading the line, do our line-finishing work */
2589
    if (evt == OS_EVT_LINE)
2590
        read_line_done(vmg0_);
2591
2592
    /* 
2593
     *   Log and return the event.  Note that we log events in the UI
2594
     *   character set, so we want to simply use the original, untranslated
2595
     *   input buffer. 
2596
     */
2597
    return log_event(vmg_ evt, S_read_buf, strlen(S_read_buf), FALSE);
2598
}
2599
2600
/*
2601
 *   Cancel an interrupted input. 
2602
 */
2603
void CVmConsole::read_line_cancel(VMG_ int reset)
2604
{
2605
    /* reset the underling OS layer */
2606
    os_gets_cancel(reset);
2607
2608
    /* do our line-ending work */
2609
    read_line_done(vmg0_);
2610
}
2611
2612
/*
2613
 *   Perform line-ending work.  This is used when we finish reading a line
2614
 *   in read_line_timeout(), or when we cancel an interrupted line, thus
2615
 *   finishing the line, in read_line_cancel(). 
2616
 */
2617
void CVmConsole::read_line_done(VMG0_)
2618
{
2619
    /* if we have a line in progress, finish it off */
2620
    if (S_read_in_progress)
2621
    {
2622
        /* set the original 'more' mode */
2623
        set_more_state(S_old_more_mode);
2624
2625
        /* 
2626
         *   Write the input line, followed by a newline, to the log file.
2627
         *   Note that the text is still in the local character set, so we
2628
         *   can write it directly to the log file.
2629
         *   
2630
         *   If we're reading from a script file in "echo" mode, skip this.
2631
         *   When reading from a script file in "echo" mode, we will manually
2632
         *   copy the input commands to the main console, which will
2633
         *   automatically copy to the main log file.  If we're in quiet
2634
         *   scripting mode, though, we won't do that, so we do need to
2635
         *   capture the input explicitly here.  
2636
         */
2637
        if (log_enabled_ && (script_sp_ == 0 || script_sp_->quiet))
2638
        {
2639
            log_str_->print_to_os(S_read_buf);
2640
            log_str_->print_to_os("\n");
2641
        }
2642
        
2643
        /* note in the streams that we've read an input line */
2644
        disp_str_->note_input_line();
2645
        if (log_str_ != 0)
2646
            log_str_->note_input_line();
2647
2648
        /* clear the in-progress flag */
2649
        S_read_in_progress = FALSE;
2650
    }
2651
}
2652
2653
/* ------------------------------------------------------------------------ */
2654
/*
2655
 *   Read an input event from the script file.  If we're reading an event
2656
 *   script file, we'll read the next event and return TRUE; if we're not
2657
 *   reading a script file, or the script file is a command-line script
2658
 *   rather than an event script, we'll simply return FALSE.
2659
 *   
2660
 *   If the event takes a parameter, we'll read the parameter into 'buf'.
2661
 *   The value is returned in the local character set, so the caller will
2662
 *   need to translate it to UTF-8.
2663
 *   
2664
 *   If 'filter' is non-null, we'll only return events of the types in the
2665
 *   filter list.  
2666
 */
2667
int CVmConsole::read_event_script(VMG_ int *evt, char *buf, size_t buflen,
2668
                                  const int *filter, int filter_cnt,
2669
                                  unsigned long *attrs)
2670
{
2671
    /* 
2672
     *   if we're not reading a script, or it's not an event script, skip
2673
     *   this 
2674
     */
2675
    if (script_sp_ == 0 || !script_sp_->event_script)
2676
        return FALSE;
2677
2678
    /* get the script file */
2679
    osfildef *fp = script_sp_->fp;
2680
2681
    /* keep going until we find something */
2682
    for (;;)
2683
    {
2684
        /* read the next event */
2685
        if (!read_script_event_type(evt, attrs))
2686
        {
2687
            /* end of the script - close it */
2688
            set_more_state(close_script_file());
2689
2690
            /* if there's no more script file, there's no event */
2691
            if (script_sp_ == 0)
2692
                return FALSE;
2693
2694
            /* go back for the next event */
2695
            fp = script_sp_->fp;
2696
            continue;
2697
        }
2698
2699
        /* if it's not in the filter list, skip it */
2700
        if (filter != 0)
2701
        {
2702
            int i, found;
2703
2704
            /* look for a match in our filter list */
2705
            for (i = 0, found = FALSE ; i < filter_cnt ; ++i)
2706
            {
2707
                if (filter[i] == *evt)
2708
                {
2709
                    found = TRUE;
2710
                    break;
2711
                }
2712
            }
2713
2714
            /* if we didn't find it, skip this line */
2715
            if (!found)
2716
            {
2717
                skip_script_line(fp);
2718
                continue;
2719
            }
2720
        }
2721
2722
        /* if there's a buffer, read the rest of the line */
2723
        if (buf != 0)
2724
        {
2725
            /* read the parameter into the buffer, and return the result */
2726
            if (!read_script_param(buf, buflen, fp))
2727
                return FALSE;
2728
2729
            /* if this is an OS_EVT_KEY event, translate special keys */
2730
            if (*evt == OS_EVT_KEY)
2731
            {
2732
                if (strcmp(buf, "[enter]") == 0)
2733
                    strcpy(buf, "\n");
2734
                else if (strcmp(buf, "[tab]") == 0)
2735
                    strcpy(buf, "\t");
2736
                else if (strcmp(buf, "[space]") == 0)
2737
                    strcpy(buf, " ");
2738
            }
2739
        }
2740
        else
2741
        {
2742
            /* no result buffer - just skip anything left on the line */
2743
            skip_script_line(fp);
2744
        }
2745
2746
        /* success */
2747
        return TRUE;
2748
    }
2749
}
2750
2751
/*
2752
 *   Read a <tag> or attribute token from a script file.  Returns the
2753
 *   character after the end of the token.  
2754
 */
2755
static int read_script_token(char *buf, size_t buflen, osfildef *fp)
2756
{
2757
    char *p;
2758
    int c;
2759
2760
    /* skip leading whitespace */
2761
    for (c = osfgetc(fp) ; isspace(c) ; c = osfgetc(fp)) ;
2762
2763
    /* read from the file until we reach the end of the token */
2764
    for (p = buf ;
2765
         p < buf + buflen - 1
2766
             && c != '>' && !isspace(c)
2767
             && c != '\n' && c != '\r' && c != EOF ; )
2768
    {
2769
        /* store this character */
2770
        *p++ = (char)c;
2771
2772
        /* get the next one */
2773
        c = osfgetc(fp);
2774
    }
2775
2776
    /* null-terminate the token */
2777
    *p = '\0';
2778
2779
    /* return the character that ended the token */
2780
    return c;
2781
}
2782
2783
/*
2784
 *   Read the next event type from current event script file.  This leaves
2785
 *   the file positioned at the parameter data for the event, if any.
2786
 *   Returns FALSE if we reach end of file without finding an event.  
2787
 */
2788
int CVmConsole::read_script_event_type(int *evt, unsigned long *attrs)
2789
{
2790
    /* clear the caller's attribute flags, if provided */
2791
    if (attrs != 0)
2792
        *attrs = 0;
2793
2794
    /* if there's no script, there's no event */
2795
    if (script_sp_ == 0)
2796
        return FALSE;
2797
2798
    /* get the file */
2799
    osfildef *fp = script_sp_->fp;
2800
2801
    /* if it's a command-line script, there are only line input events */
2802
    if (!script_sp_->event_script)
2803
    {
2804
        /* keep going until we find an input line */
2805
        for (;;)
2806
        {
2807
            /* read the first charater of the line */
2808
            int c = osfgetc(fp);
2809
            if (c == '>')
2810
            {
2811
                /* we found a line input event */
2812
                *evt = OS_EVT_LINE;
2813
                return TRUE;
2814
            }
2815
            else if (c == EOF)
2816
            {
2817
                /* end of file - give up */
2818
                return FALSE;
2819
            }
2820
            else
2821
            {
2822
                /* 
2823
                 *   anything else is just a comment line - just skip it and
2824
                 *   keep looking 
2825
                 */
2826
                skip_script_line(fp);
2827
            }
2828
        }
2829
    }
2830
2831
    /* keep going until we find an event tag */
2832
    for (;;)
2833
    {
2834
        int c;
2835
        char tag[32];
2836
        static const struct
2837
        {
2838
            const char *tag;
2839
            int evt;
2840
        }
2841
        *tp, tags[] =
2842
        {
2843
            { "key", OS_EVT_KEY },
2844
            { "timeout", OS_EVT_TIMEOUT },
2845
            { "notimeout", OS_EVT_NOTIMEOUT },
2846
            { "eof", OS_EVT_EOF },
2847
            { "line", OS_EVT_LINE },
2848
            { "command", OS_EVT_COMMAND },
2849
            
2850
            { "endqs", VMCON_EVT_END_SCRIPT },
2851
            { "dialog", VMCON_EVT_DIALOG },
2852
            { "file", VMCON_EVT_FILE },
2853
2854
            { 0, 0 }
2855
        };
2856
2857
        /* check the start of the line to make sure it's an event */
2858
        c = osfgetc(fp);
2859
2860
        /* if at EOF, return failure */
2861
        if (c == EOF)
2862
            return FALSE;
2863
2864
        /* if it's not an event code line, skip the line */
2865
        if (c != '<')
2866
        {
2867
            skip_script_line(fp);
2868
            continue;
2869
        }
2870
2871
        /* read the event type (up to the '>') */
2872
        c = read_script_token(tag, sizeof(tag), fp);
2873
2874
        /* check for attributes */
2875
        while (isspace(c))
2876
        {
2877
            char attr[32];
2878
            static const struct
2879
            {
2880
                const char *name;
2881
                unsigned long flag;
2882
            }
2883
            *ap, attrlist[] =
2884
            {
2885
                { "overwrite", VMCON_EVTATTR_OVERWRITE },
2886
                { 0, 0 }
2887
            };
2888
2889
            /* read the attribute name */
2890
            c = read_script_token(attr, sizeof(attr), fp);
2891
2892
            /* if the name is empty, stop */
2893
            if (attr[0] == '\0')
2894
                break;
2895
2896
            /* look up the token */
2897
            for (ap = attrlist ; ap->name != 0 ; ++ap)
2898
            {
2899
                /* check for a match */
2900
                if (stricmp(attr, ap->name) == 0)
2901
                {
2902
                    /* if the caller wants the flag, set it */
2903
                    if (attrs != 0)
2904
                        *attrs |= ap->flag;
2905
2906
                    /* no need to look any further */
2907
                    break;
2908
                }
2909
            }
2910
2911
            /* if we're at the '>' or at end of line or file, stop */
2912
            if (c == '>' || c == '\n' || c == '\r' || c == EOF)
2913
                break;
2914
        }
2915
2916
        /* if it's not a well-formed tag, ignore it */
2917
        if (c != '>')
2918
        {
2919
            skip_script_line(fp);
2920
            continue;
2921
        }
2922
2923
        /* look up the tag */
2924
        for (tp = tags ; tp->tag != 0 ; ++tp)
2925
        {
2926
            /* check for a match to this tag name */
2927
            if (stricmp(tp->tag, tag) == 0)
2928
            {
2929
                /* got it - return the event type */
2930
                *evt = tp->evt;
2931
                return TRUE;
2932
            }
2933
        }
2934
2935
        /* we don't recognize the tag name; skip the line and keep looking */
2936
        skip_script_line(fp);
2937
    }
2938
}
2939
2940
/*
2941
 *   Skip to the next script line 
2942
 */
2943
void CVmConsole::skip_script_line(osfildef *fp)
2944
{
2945
    int c;
2946
2947
    /* read until we find the end of the current line */
2948
    for (c = osfgetc(fp) ; c != EOF && c != '\n' && c != '\r' ;
2949
         c = osfgetc(fp)) ;
2950
}
2951
2952
/*
2953
 *   read the rest of the current script line into the given buffer 
2954
 */
2955
int CVmConsole::read_script_param(char *buf, size_t buflen, osfildef *fp)
2956
{
2957
    /* ignore zero-size buffer requests */
2958
    if (buflen == 0)
2959
        return FALSE;
2960
2961
    /* read characters until we run out of buffer or reach a newline */
2962
    for (;;)
2963
    {
2964
        /* get the next character */
2965
        int c = osfgetc(fp);
2966
2967
        /* if it's a newline or end of file, we're done */
2968
        if (c == '\n' || c == EOF)
2969
        {
2970
            /* null-terminate the buffer */
2971
            *buf = '\0';
2972
2973
            /* indicate success */
2974
            return TRUE;
2975
        }
2976
2977
        /* 
2978
         *   if there's room in the buffer, add the character - always leave
2979
         *   one byte for the null terminator 
2980
         */
2981
        if (buflen > 1)
2982
        {
2983
            *buf++ = (char)c;
2984
            --buflen;
2985
        }
2986
    }
2987
}
2988
2989
2990
/*
2991
 *   Read a line of text from the script file, if there is one.  Returns TRUE
2992
 *   on success, FALSE if we reach the end of the script file or encounter
2993
 *   any other error.  
2994
 */
2995
int CVmConsole::read_line_from_script(char *buf, size_t buflen, int *evt)
2996
{
2997
    /* if there's no script file, return failure */
2998
    if (script_sp_ == 0)
2999
        return FALSE;
3000
3001
    /* get the file from the script stack */
3002
    osfildef *fp = script_sp_->fp;
3003
3004
    /* keep going until we find a line that we like */
3005
    for (;;)
3006
    {
3007
        /* read the script according to its type ('event' or 'line input') */
3008
        if (script_sp_->event_script)
3009
        {
3010
            /* read to the next event */
3011
            if (!read_script_event_type(evt, 0))
3012
                return FALSE;
3013
3014
            /* check the event code */
3015
            switch (*evt)
3016
            {
3017
            case OS_EVT_LINE:
3018
            case OS_EVT_TIMEOUT:
3019
                /* 
3020
                 *   it's one of our line input events - read the line (or
3021
                 *   partial line, in the case of TIMEOUT) 
3022
                 */
3023
                return read_script_param(buf, buflen, fp);
3024
3025
            default:
3026
                /* 
3027
                 *   it's not our type of event - skip the rest of the line
3028
                 *   and keep looking 
3029
                 */
3030
                skip_script_line(fp);
3031
                break;
3032
            }
3033
        }
3034
        else
3035
        {
3036
            /* 
3037
             *   We have a basic line-input script rather than an event
3038
             *   script.  Each input line starts with a '>'; everything else
3039
             *   is a comment.
3040
             *   
3041
             *   Read the first character on the line.  If it's not a
3042
             *   newline, there's more text on the same line, so read the
3043
             *   rest and determine what to do.  
3044
             */
3045
            int c = osfgetc(fp);
3046
            if (c == '>')
3047
            {
3048
                /* it's a command line - read it */
3049
                *evt = OS_EVT_LINE;
3050
                return read_script_param(buf, buflen, fp);
3051
            }
3052
            else if (c == EOF)
3053
            {
3054
                /* end of file */
3055
                return FALSE;
3056
            }
3057
            else if (c == '\n' || c == '\r')
3058
            {
3059
                /* blank line - continue on to the next line */
3060
            }
3061
            else
3062
            {
3063
                /* it's not a command line - just skip it and keep looking */
3064
                skip_script_line(fp);
3065
            }
3066
        }
3067
    }
3068
}
3069
3070
/* ------------------------------------------------------------------------ */
3071
/*
3072
 *   Main System Console 
3073
 */
3074
3075
/*
3076
 *   create 
3077
 */
3078
CVmConsoleMain::CVmConsoleMain(VMG0_)
3079
{
3080
    /* create the system banner manager */
3081
    banner_manager_ = new CVmBannerManager();
3082
3083
    /* create the log console manager */
3084
    log_console_manager_ = new CVmLogConsoleManager();
3085
3086
    /* create and initialize our display stream */
3087
    main_disp_str_ = new CVmFormatterMain(this, 256);
3088
    main_disp_str_->init();
3089
3090
    /* initially send text to the main display stream */
3091
    disp_str_ = main_disp_str_;
3092
3093
    /* 
3094
     *   Create our log stream.  The main console always has a log stream,
3095
     *   even when it's not in use, so that we can keep the log stream's
3096
     *   state synchronized with the display stream in preparation for
3097
     *   activation.  
3098
     */
3099
    log_str_ = new CVmFormatterLog(this, 80);
3100
3101
    /* 
3102
     *   use the default log file character mapper - on some systems, files
3103
     *   don't use the same character set as the display 
3104
     */
3105
    log_str_->set_charmap(G_cmap_to_log);
3106
3107
    /* initialize the log stream */
3108
    log_str_->init();
3109
3110
    /* 
3111
     *   the log stream is initially enabled (this is separate from the log
3112
     *   file being opened; it merely indicates that we send output
3113
     *   operations to the log stream for processing) 
3114
     */
3115
    log_enabled_ = TRUE;
3116
3117
    /* we don't have a statusline formatter until asked for one */
3118
    statline_str_ = 0;
3119
3120
    /* reset statics */
3121
    S_read_in_progress = FALSE;
3122
    S_read_buf[0] = '\0';
3123
}
3124
3125
/*
3126
 *   delete 
3127
 */
3128
CVmConsoleMain::~CVmConsoleMain()
3129
{
3130
    /* delete the system banner manager */
3131
    banner_manager_->delete_obj();
3132
3133
    /* delete the system log console manager */
3134
    log_console_manager_->delete_obj();
3135
3136
    /* delete the display stream */
3137
    delete main_disp_str_;
3138
3139
    /* delete the statusline stream, if we have one */
3140
    if (statline_str_ != 0)
3141
        delete statline_str_;
3142
}
3143
3144
/*
3145
 *   Clear the window 
3146
 */
3147
void CVmConsoleMain::clear_window(VMG0_)
3148
{
3149
    /* flush and empty our output buffer */
3150
    flush(vmg_ VM_NL_NONE);
3151
    empty_buffers(vmg0_);
3152
    
3153
    /* clear the main window */
3154
    oscls();
3155
3156
    /* reset the MORE line counter in the display stream */
3157
    disp_str_->reset_line_count(TRUE);
3158
}
3159
3160
/*
3161
 *   Set statusline mode 
3162
 */
3163
void CVmConsoleMain::set_statusline_mode(VMG_ int mode)
3164
{
3165
    CVmFormatterDisp *str;
3166
3167
    /* 
3168
     *   if we're switching into statusline mode, and we don't have a
3169
     *   statusline stream yet, create one 
3170
     */
3171
    if (mode && statline_str_ == 0)
3172
    {
3173
        /* create and initialize the statusline stream */
3174
        statline_str_ = new CVmFormatterStatline(this);
3175
        statline_str_->init();
3176
    }
3177
3178
    /* get the stream selected by the new mode */
3179
    if (mode)
3180
        str = statline_str_;
3181
    else
3182
        str = main_disp_str_;
3183
3184
    /* if this is already the active stream, we have nothing more to do */
3185
    if (str == disp_str_)
3186
        return;
3187
3188
    /* make the new stream current */
3189
    disp_str_ = str;
3190
3191
    /* 
3192
     *   check which mode we're switching to, so we can do some extra work
3193
     *   specific to each mode 
3194
     */
3195
    if (mode)
3196
    {
3197
        /* 
3198
         *   we're switching to the status line, so disable the log stream -
3199
         *   statusline text is never sent to the log, since the log reflects
3200
         *   only what was displayed in the main text area 
3201
         */
3202
        log_enabled_ = FALSE;
3203
    }
3204
    else
3205
    {
3206
        /*
3207
         *   we're switching back to the main stream, so flush the statusline
3208
         *   so we're sure the statusline text is displayed 
3209
         */
3210
3211
        /* end the line */
3212
        statline_str_->format_text(vmg_ "\n", 1);
3213
3214
        /* flush output */
3215
        statline_str_->flush(vmg_ VM_NL_NONE);
3216
3217
        /* re-enable the log stream, if we have one */
3218
        if (log_str_ != 0)
3219
            log_enabled_ = TRUE;
3220
    }
3221
3222
    /* switch at the OS layer */
3223
    os_status(mode);
3224
}
3225
3226
/*
3227
 *   Flush everything 
3228
 */
3229
void CVmConsoleMain::flush_all(VMG_ vm_nl_type nl)
3230
{
3231
    /* flush our primary console */
3232
    flush(vmg_ nl);
3233
3234
    /* 
3235
     *   Flush each banner we're controlling.  Note that we explicitly flush
3236
     *   the banners with newline mode 'NONE', regardless of the newline mode
3237
     *   passed in by the caller: the caller's mode is for the primary
3238
     *   console, but for the banners we just want to make sure they're
3239
     *   flushed out normally, since whatever we're doing in the primary
3240
     *   console that requires flushing doesn't concern the banners. 
3241
     */
3242
    banner_manager_->flush_all(vmg_ VM_NL_NONE);
3243
}
3244
3245
/* ------------------------------------------------------------------------ */
3246
/*
3247
 *   Handle manager 
3248
 */
3249
3250
/* initialize */
3251
CVmHandleManager::CVmHandleManager()
3252
{
3253
    size_t i;
3254
3255
    /* allocate an initial array of handle slots */
3256
    handles_max_ = 32;
3257
    handles_ = (void **)t3malloc(handles_max_ * sizeof(*handles_));
3258
3259
    /* all slots are initially empty */
3260
    for (i = 0 ; i < handles_max_ ; ++i)
3261
        handles_[i] = 0;
3262
}
3263
3264
/* delete the object - this is the public destructor interface */
3265
void CVmHandleManager::delete_obj()
3266
{
3267
    size_t i;
3268
3269
    /* 
3270
     *   Delete each remaining object.  Note that we need to call the virtual
3271
     *   delete_handle_object routine, so we must do this before reaching the
3272
     *   destructor (once in the base class destructor, we no longer have
3273
     *   access to the subclass virtuals).  
3274
     */
3275
    for (i = 0 ; i < handles_max_ ; ++i)
3276
    {
3277
        /* if this banner is still valid, delete it */
3278
        if (handles_[i] != 0)
3279
            delete_handle_object(i + 1, handles_[i]);
3280
    }
3281
3282
    /* delete the object */
3283
    delete this;
3284
}
3285
3286
/* destructor */
3287
CVmHandleManager::~CVmHandleManager()
3288
{
3289
    /* delete the handle pointer array */
3290
    t3free(handles_);
3291
}
3292
3293
/* 
3294
 *   Allocate a new handle 
3295
 */
3296
int CVmHandleManager::alloc_handle(void *item)
3297
{
3298
    size_t slot;
3299
3300
    /* scan for a free slot */
3301
    for (slot = 0 ; slot < handles_max_ ; ++slot)
3302
    {
3303
        /* if this one is free, use it */
3304
        if (handles_[slot] == 0)
3305
            break;
3306
    }
3307
3308
    /* if we didn't find a free slot, extend the array */
3309
    if (slot == handles_max_)
3310
    {
3311
        size_t i;
3312
3313
        /* allocate a larger array */
3314
        handles_max_ += 32;
3315
        handles_ = (void **)
3316
                   t3realloc(handles_, handles_max_ * sizeof(*handles_));
3317
3318
        /* clear out the newly-allocated slots */
3319
        for (i = slot ; i < handles_max_ ; ++i)
3320
            handles_[i] = 0;
3321
    }
3322
3323
    /* store the new item in our pointer array */
3324
    handles_[slot] = item;
3325
3326
    /* 
3327
     *   convert the slot number to a handle by adjusting it to a 1-based
3328
     *   index, and return the result 
3329
     */
3330
    return slot + 1;
3331
}
3332
3333
3334
/* ------------------------------------------------------------------------ */
3335
/*
3336
 *   Banner manager 
3337
 */
3338
3339
/*
3340
 *   Create a banner 
3341
 */
3342
int CVmBannerManager::create_banner(VMG_ int parent_id,
3343
                                    int where, int other_id,
3344
                                    int wintype, int align,
3345
                                    int siz, int siz_units,
3346
                                    unsigned long style)
3347
{
3348
    void *handle;
3349
    void *parent_handle;
3350
    void *other_handle;
3351
    CVmConsoleBanner *item;
3352
3353
    /* get the parent handle, if provided */
3354
    parent_handle = get_os_handle(parent_id);
3355
3356
    /* get the 'other' handle, if we need it for the 'where' */
3357
    switch(where)
3358
    {
3359
    case OS_BANNER_BEFORE:
3360
    case OS_BANNER_AFTER:
3361
        /* retrieve the handle for the other_id */
3362
        other_handle = get_os_handle(other_id);
3363
        break;
3364
3365
    default:
3366
        /* we don't need 'other' for other 'where' modes */
3367
        other_handle = 0;
3368
        break;
3369
    }
3370
3371
    /* try creating the OS-level banner window */
3372
    handle = os_banner_create(parent_handle, where, other_handle, wintype,
3373
                              align, siz, siz_units, style);
3374
3375
    /* if we couldn't create the OS-level window, return failure */
3376
    if (handle == 0)
3377
        return 0;
3378
3379
    /* create the new console */
3380
    item = new CVmConsoleBanner(handle, wintype, style);
3381
3382
    /* allocate a handle for the new banner, and return the handle */
3383
    return alloc_handle(item);
3384
}
3385
3386
/*
3387
 *   Delete or orphan a banner window.  Deleting and orphaning both sever
3388
 *   all ties from the banner manager (and thus from the T3 program) to the
3389
 *   banner.  Deleting a banner actually gets deletes it at the OS level;
3390
 *   orphaning the banner severs our ties, but hands the banner over to the
3391
 *   OS to do with as it pleases.  On some implementations, the OS will
3392
 *   continue to display the banner after it's orphaned to allow the final
3393
 *   display configuration to remain visible even after the program has
3394
 *   terminated.  
3395
 */
3396
void CVmBannerManager::delete_or_orphan_banner(int banner, int orphan)
3397
{
3398
    CVmConsoleBanner *item;
3399
    void *handle;
3400
3401
    /* if the banner is invalid, ignore the request */
3402
    if ((item = (CVmConsoleBanner *)get_object(banner)) == 0)
3403
        return;
3404
3405
    /* get the OS-level banner handle */
3406
    handle = item->get_os_handle();
3407
3408
    /* delete the banner item */
3409
    delete item;
3410
3411
    /* clear the slot */
3412
    clear_handle(banner);
3413
3414
    /* delete the OS-level banner */
3415
    if (orphan)
3416
        os_banner_orphan(handle);
3417
    else
3418
        os_banner_delete(handle);
3419
}
3420
3421
/*
3422
 *   Get the OS-level handle for the given banner 
3423
 */
3424
void *CVmBannerManager::get_os_handle(int banner)
3425
{
3426
    CVmConsoleBanner *item;
3427
3428
    /* if the banner is invalid, return failure */
3429
    if ((item = (CVmConsoleBanner *)get_object(banner)) == 0)
3430
        return 0;
3431
3432
    /* return the handle from the slot */
3433
    return item->get_os_handle();
3434
}
3435
3436
/*
3437
 *   Flush all banners 
3438
 */
3439
void CVmBannerManager::flush_all(VMG_ vm_nl_type nl)
3440
{
3441
    size_t slot;
3442
3443
    /* flush each banner */
3444
    for (slot = 0 ; slot < handles_max_ ; ++slot)
3445
    {
3446
        /* if this slot has a valid banner, flush it */
3447
        if (handles_[slot] != 0)
3448
            ((CVmConsoleBanner *)handles_[slot])->flush(vmg_ nl);
3449
    }
3450
}
3451
3452
/* ------------------------------------------------------------------------ */
3453
/*
3454
 *   Banner Window Console 
3455
 */
3456
CVmConsoleBanner::CVmConsoleBanner(void *banner_handle, int win_type,
3457
                                   unsigned long style)
3458
{
3459
    CVmFormatterBanner *str;
3460
    os_banner_info_t info;
3461
    int obey_whitespace = FALSE;
3462
    int literal_mode = FALSE;
3463
3464
    /* remember our OS-level banner handle */
3465
    banner_ = banner_handle;
3466
3467
    /* get osifc-level information on the banner */
3468
    if (!os_banner_getinfo(banner_, &info))
3469
        info.os_line_wrap = FALSE;
3470
3471
    /* 
3472
     *   If it's a text grid window, don't do any line wrapping.  Text grids
3473
     *   simply don't have any line wrapping, so we don't want to impose any
3474
     *   at the formatter level.  Set the formatter to "os line wrap" mode,
3475
     *   to indicate that the formatter doesn't do wrapping - even though
3476
     *   the underlying OS banner window won't do any wrapping either, the
3477
     *   lack of line wrapping counts as OS handling of line wrapping.  
3478
     */
3479
    if (win_type == OS_BANNER_TYPE_TEXTGRID)
3480
    {
3481
        /* do not wrap lines in the formatter */
3482
        info.os_line_wrap = TRUE;
3483
3484
        /* use literal mode, and obey whitespace literally */
3485
        literal_mode = TRUE;
3486
        obey_whitespace = TRUE;
3487
    }
3488
3489
    /* create and initialize our display stream */
3490
    disp_str_ = str = new CVmFormatterBanner(banner_handle, this,
3491
                                             win_type, style);
3492
    str->init_banner(info.os_line_wrap, obey_whitespace, literal_mode);
3493
3494
    /* remember our window type */
3495
    win_type_ = win_type;
3496
}
3497
3498
/*
3499
 *   Deletion 
3500
 */
3501
CVmConsoleBanner::~CVmConsoleBanner()
3502
{
3503
    /* delete our display stream */
3504
    delete disp_str_;
3505
}
3506
3507
/*
3508
 *   Clear the banner window 
3509
 */
3510
void CVmConsoleBanner::clear_window(VMG0_)
3511
{
3512
    /* flush and empty our output buffer */
3513
    flush(vmg_ VM_NL_NONE);
3514
    empty_buffers(vmg0_);
3515
    
3516
    /* clear our underlying system banner */
3517
    os_banner_clear(banner_);
3518
    
3519
    /* tell our display stream to zero its line counter */
3520
    disp_str_->reset_line_count(TRUE);
3521
}
3522
3523
/*
3524
 *   Get banner information 
3525
 */
3526
int CVmConsoleBanner::get_banner_info(os_banner_info_t *info)
3527
{
3528
    int ret;
3529
    
3530
    /* get the OS-level information */
3531
    ret = os_banner_getinfo(banner_, info);
3532
3533
    /* make some adjustments if we got valid information back */
3534
    if (ret)
3535
    {
3536
        /* 
3537
         *   check the window type for further adjustments we might need to
3538
         *   make to the data returned from the OS layer 
3539
         */
3540
        switch(win_type_)
3541
        {
3542
        case OS_BANNER_TYPE_TEXTGRID:
3543
            /* 
3544
             *   text grids don't support <TAB> alignment, even if the
3545
             *   underlying OS banner says we do, because we simply don't
3546
             *   support <TAB> (or any other HTML markups) in a text grid
3547
             *   window 
3548
             */
3549
            info->style &= ~OS_BANNER_STYLE_TAB_ALIGN;
3550
            break;
3551
3552
        default:
3553
            /* other types don't require any adjustments */
3554
            break;
3555
        }
3556
    }
3557
3558
    /* return the success indication */
3559
    return ret;
3560
}
3561
3562
/* ------------------------------------------------------------------------ */
3563
/*
3564
 *   Log file console manager 
3565
 */
3566
3567
/*
3568
 *   create a log console 
3569
 */
3570
int CVmLogConsoleManager::create_log_console(const char *fname,
3571
                                             osfildef *fp,
3572
                                             class CCharmapToLocal *cmap,
3573
                                             int width)
3574
{
3575
    CVmConsoleLog *con;
3576
    
3577
    /* create the new console */
3578
    con = new CVmConsoleLog(fname, fp, cmap, width);
3579
3580
    /* allocate a handle for the new console and return the handle */
3581
    return alloc_handle(con);
3582
}
3583
3584
/*
3585
 *   delete log a console 
3586
 */
3587
void CVmLogConsoleManager::delete_log_console(int handle)
3588
{
3589
    CVmConsoleLog *con;
3590
    
3591
    /* if the handle is invalid, ignore the request */
3592
    if ((con = (CVmConsoleLog *)get_object(handle)) == 0)
3593
        return;
3594
3595
    /* delete the console */
3596
    delete con;
3597
3598
    /* clear the slot */
3599
    clear_handle(handle);
3600
}
3601
3602
/* ------------------------------------------------------------------------ */
3603
/*
3604
 *   Log file console 
3605
 */
3606
CVmConsoleLog::CVmConsoleLog(const char *fname, osfildef *fp,
3607
                             class CCharmapToLocal *cmap, int width)
3608
{
3609
    CVmFormatterLog *str;
3610
3611
    /* create our display stream */
3612
    disp_str_ = str = new CVmFormatterLog(this, width);
3613
3614
    /* set the file */
3615
    str->set_log_file(fname, fp);
3616
3617
    /* set the character mapper */
3618
    str->set_charmap(cmap);
3619
}
3620
3621
/*
3622
 *   destroy 
3623
 */
3624
CVmConsoleLog::~CVmConsoleLog()
3625
{
3626
    /* delete our display stream */
3627
    delete disp_str_;
3628
}
3629