cfad47cfa3/tads3/vmconsol.cpp
Commiter: Nikos Chantziaras
Author: Nikos Chantziaras
Revision: cfad47cfa3
File Size: 110 KB
(June 01, 2009 20:54 UTC) Almost 3 years ago
Initial commit.
Showing without highlighting since it looks like a big file and may slow your browser - show with highlighting
Show/hide line numbers#ifdef RCSID
static char RCSid[] =
"$Header$";
#endif
/*
* Copyright (c) 1987, 2002 Michael J. Roberts. All Rights Reserved.
*
* Please see the accompanying license file, LICENSE.TXT, for information
* on using and copying this software.
*/
/*
Name
vmconsol.cpp - TADS 3 console input reader and output formatter
Function
Provides console input and output for the TADS 3 built-in function set
for the T3 VM, including the output formatter.
T3 uses the UTF-8 character set to represent character strings. The OS
functions use the local character set. We perform the mapping between
UTF-8 and the local character set within this module, so that OS routines
see local characters only, not UTF-8.
This code is based on the TADS 2 output formatter, but has been
substantially reworked for C++, Unicode, and the slightly different
TADS 3 formatting model.
Notes
Returns
None
Modified
08/25/99 MJRoberts - created from TADS 2 output formatter
*/
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include "wchar.h"
#include "os.h"
#include "t3std.h"
#include "utf8.h"
#include "charmap.h"
#include "vmuni.h"
#include "vmconsol.h"
#include "vmglob.h"
#include "vmhash.h"
/* ------------------------------------------------------------------------ */
/*
* Log-file formatter subclass implementation
*/
/*
* delete
*/
CVmFormatterLog::~CVmFormatterLog()
{
/* close any active log file */
close_log_file();
}
/*
* Open a new log file
*/
int CVmFormatterLog::open_log_file(const char *fname)
{
/* close any existing log file */
if (close_log_file())
return 1;
/* reinitialize */
init();
/* save the filename for later (we'll need it when we close the file) */
logfname_ = lib_copy_str(fname);
/* open the new file */
logfp_ = osfopwt(fname, OSFTLOG);
/* return success if we successfully opened the file, failure otherwise */
return (logfp_ == 0);
}
/*
* Set the log file to a file opened by the caller
*/
int CVmFormatterLog::set_log_file(const char *fname, osfildef *fp)
{
/* close any existing log file */
if (close_log_file())
return 1;
/* reinitialize */
init();
/* remember the file */
logfp_ = fp;
/* remember the filename */
logfname_ = lib_copy_str(fname);
/* success */
return 0;
}
/*
* Close the log file
*/
int CVmFormatterLog::close_log_file()
{
/* if we have a file, close it */
if (logfp_ != 0)
{
/* close the handle */
osfcls(logfp_);
/* forget about our log file handle */
logfp_ = 0;
/* set the system file type to "log file" */
if (logfname_ != 0)
os_settype(logfname_, OSFTLOG);
}
/* forget the log file name, if we have one */
if (logfname_ != 0)
{
lib_free_str(logfname_);
logfname_ = 0;
}
/* success */
return 0;
}
/* ------------------------------------------------------------------------ */
/*
* Base Formatter
*/
/*
* deletion
*/
CVmFormatter::~CVmFormatter()
{
/* if we have a table of horizontal tabs, delete it */
if (tabs_ != 0)
delete tabs_;
/* forget the character mapper */
set_charmap(0);
}
/*
* set a new character mapper
*/
void CVmFormatter::set_charmap(CCharmapToLocal *cmap)
{
/* add a reference to the new mapper, if we have one */
if (cmap != 0)
cmap->add_ref();
/* release our reference on any old mapper */
if (cmap_ != 0)
cmap_->release_ref();
/* remember the new mapper */
cmap_ = cmap;
}
/*
* Write out a line. Text we receive is in the UTF-8 character set.
*/
void CVmFormatter::write_text(VMG_ const wchar_t *txt, size_t cnt,
const vmcon_color_t *colors, vm_nl_type nl)
{
/*
* Check the "script quiet" mode - this indicates that we're reading
* a script and not echoing output to the display. If this mode is
* on, and we're writing to the display, suppress this write. If
* the mode is off, or we're writing to a non-display stream (such
* as a log file stream), show the output as normal.
*/
if (!console_->is_quiet_script() || !is_disp_stream_)
{
char local_buf[128];
char *dst;
size_t rem;
/*
* Check to see if we've reached the end of the screen, and if so
* run the MORE prompt. Note that we don't show a MORE prompt
* unless we're in "formatter more mode," since if we're not, then
* the OS layer code is taking responsibility for pagination
* issues.
*
* Note that we suppress the MORE prompt if we're showing a
* continuation of a line already partially shown. We only want to
* show a MORE prompt at the start of a new line.
*
* Skip the MORE prompt if this stream doesn't use it.
*/
if (formatter_more_mode()
&& console_->is_more_mode()
&& !is_continuation_
&& linecnt_ + 1 >= console_->get_page_length())
{
/* set the standard text color */
set_os_text_color(OS_COLOR_P_TEXT, OS_COLOR_P_TEXTBG);
set_os_text_attr(0);
/* display the MORE prompt */
console_->show_more_prompt(vmg0_);
/* restore the current color scheme */
set_os_text_color(os_color_.fg, os_color_.bg);
set_os_text_attr(os_color_.attr);
}
/* count the line if a newline follows */
if (nl != VM_NL_NONE && nl != VM_NL_NONE_INTERNAL)
++linecnt_;
/* convert and display the text */
for (dst = local_buf, rem = sizeof(local_buf) - 1 ; cnt != 0 ; )
{
size_t cur;
size_t old_rem;
wchar_t c;
/*
* if this character is in a new color, write out the OS-level
* color switch code
*/
if (colors != 0 && !colors->equals(&os_color_))
{
/*
* null-terminate and display what's in the buffer so far,
* so that we close out all of the remaining text in the
* old color and attributes
*/
*dst = '\0';
print_to_os(local_buf);
/* reset to the start of the local output buffer */
dst = local_buf;
rem = sizeof(local_buf) - 1;
/* set the text attributes, if they changed */
if (colors->attr != os_color_.attr)
set_os_text_attr(colors->attr);
/* set the color, if it changed */
if (colors->fg != os_color_.fg
|| colors->bg != os_color_.bg)
set_os_text_color(colors->fg, colors->bg);
/*
* Whatever happened, set our new color internally as the
* last color we sent to the OS. Even if we didn't
* actually do anything, we'll at least know we won't have
* to do anything more until we find another new color.
*/
os_color_ = *colors;
}
/* get this character */
c = *txt;
/*
* translate non-breaking spaces into ordinary spaces if the
* underlying target isn't HTML-based
*/
if (!html_target_ && c == 0x00A0)
c = ' ';
/* try storing another character */
old_rem = rem;
cur = (cmap_ != 0 ? cmap_ : G_cmap_to_ui)->map(c, &dst, &rem);
/* if that failed, flush the buffer and try again */
if (cur > old_rem)
{
/* null-terminate the buffer */
*dst = '\0';
/* display the text */
print_to_os(local_buf);
/* reset to the start of the local output buffer */
dst = local_buf;
rem = sizeof(local_buf) - 1;
}
else
{
/* we've now consumed this character of input */
++txt;
--cnt;
if (colors != 0)
++colors;
}
}
/* if we have a partially-filled buffer, display it */
if (dst > local_buf)
{
/* null-terminate and display the buffer */
*dst = '\0';
print_to_os(local_buf);
}
/* write the appropriate type of line termination */
switch(nl)
{
case VM_NL_NONE:
case VM_NL_INPUT:
case VM_NL_NONE_INTERNAL:
/* no line termination is needed */
break;
case VM_NL_NEWLINE:
/* write a newline */
print_to_os(html_target_ ? "<BR HEIGHT=0>\n" : "\n");
break;
case VM_NL_OSNEWLINE:
/*
* the OS will provide a newline, but add a space to make it
* explicit that we can break the line here
*/
print_to_os(" ");
break;
}
}
}
/* ------------------------------------------------------------------------ */
/*
* Flush the current line to the display, using the given type of line
* termination.
*
* VM_NL_NONE: flush the current line but do not start a new line; more
* text will follow on the current line. This is used, for example, to
* flush text after displaying a prompt and before waiting for user
* input.
*
* VM_NL_INPUT: acts like VM_NL_NONE, except that we flush everything,
* including trailing spaces.
*
* VM_NL_NONE_INTERNAL: same as VM_NL_NONE, but doesn't flush at the OS
* level. This is used when we're only flushing our buffers in order to
* clear out space internally, not because we want the underlying OS
* renderer to display things immediately. This distinction is
* important in HTML mode, since it ensures that the HTML parser only
* sees well-formed strings when flushing.
*
* VM_NL_NEWLINE: flush the line and start a new line by writing out a
* newline character.
*
* VM_NL_OSNEWLINE: flush the line as though starting a new line, but
* don't add an actual newline character to the output, since the
* underlying OS display code will handle this. Instead, add a space
* after the line to indicate to the OS code that a line break is
* possible there. (This differs from VM_NL_NONE in that VM_NL_NONE
* doesn't add anything at all after the line.)
*/
void CVmFormatter::flush(VMG_ vm_nl_type nl)
{
int cnt;
vm_nl_type write_nl;
/* null-terminate the current output line buffer */
linebuf_[linepos_] = '\0';
/*
* Expand any pending tab. Allow "anonymous" tabs only if we're
* flushing because we're ending the line normally; if we're not
* ending the line, we can't handle tabs that depend on the line
* ending.
*/
expand_pending_tab(vmg_ nl == VM_NL_NEWLINE);
/*
* note number of characters to display - assume we'll display all of
* the characters in the buffer
*/
cnt = wcslen(linebuf_);
/*
* Trim trailing spaces, unless we're about to read input or are doing
* an internal flush. (Show trailing spaces when reading input, since
* we won't be able to revise the layout after this point. Don't trim
* on an internal flush either, as this kind of flushing simply empties
* out our buffer exactly as it is.)
*/
if (nl != VM_NL_INPUT && nl != VM_NL_NONE_INTERNAL)
{
/*
* look for last non-space character, but keep any spaces that come
* before an explicit non-breaking flag
*/
for ( ; cnt > 0 && linebuf_[cnt-1] == ' ' ; --cnt)
{
/* don't remove this character if it's marked as non-breaking */
if ((flagbuf_[cnt-1] & VMCON_OBF_NOBREAK) != 0)
break;
}
/*
* if we're actually doing a newline, discard the trailing spaces
* for good - we don't want them at the start of the next line
*/
if (nl == VM_NL_NEWLINE)
linepos_ = cnt;
}
/* check the newline mode */
switch(nl)
{
case VM_NL_NONE:
case VM_NL_NONE_INTERNAL:
/* no newline - just flush out what we have */
write_nl = VM_NL_NONE;
break;
case VM_NL_INPUT:
/* no newline - flush out what we have */
write_nl = VM_NL_NONE;
/* on input, reset the HTML parsing state */
html_passthru_state_ = VMCON_HPS_NORMAL;
break;
case VM_NL_NEWLINE:
/*
* We're adding a newline. We want to suppress redundant
* newlines -- we reduce any run of consecutive vertical
* whitespace to a single newline. So, if we have anything in
* this line, or we didn't already just write a newline, write
* out a newline now; otherwise, write nothing.
*/
if (linecol_ != 0 || !just_did_nl_)
{
/* add the newline */
write_nl = VM_NL_NEWLINE;
}
else
{
/*
* Don't write out a newline after all - the line buffer is
* empty, and we just wrote a newline, so this is a
* redundant newline that we wish to suppress (so that we
* collapse a run of vertical whitespace down to a single
* newline).
*/
write_nl = VM_NL_NONE;
}
break;
case VM_NL_OSNEWLINE:
/*
* we're going to depend on the underlying OS output layer to do
* line breaking, so we won't add a newline, but we will add a
* space, so that the underlying OS layer knows we have a word
* break here
*/
write_nl = VM_NL_OSNEWLINE;
break;
}
/*
* display the line, as long as we have something buffered to
* display; even if we don't, display it if our column is non-zero
* and we didn't just do a newline, since this must mean that we've
* flushed a partial line and are just now doing the newline
*/
if (cnt != 0 || (linecol_ != 0 && !just_did_nl_))
{
/* write it out */
write_text(vmg_ linebuf_, cnt, colorbuf_, write_nl);
}
/* check the line ending */
switch (nl)
{
case VM_NL_NONE:
case VM_NL_INPUT:
/* we're not displaying a newline, so flush what we have */
flush_to_os();
/*
* the subsequent buffer will be a continuation of the current
* text, if we've displayed anything at all here
*/
is_continuation_ = (linecol_ != 0);
break;
case VM_NL_NONE_INTERNAL:
/*
* internal buffer flush only - subsequent text will be a
* continuation of the current line, if there's anything on the
* current line
*/
is_continuation_ = (linecol_ != 0);
break;
default:
/* we displayed a newline, so reset the column position */
linecol_ = 0;
/* the next buffer starts a new line on the display */
is_continuation_ = FALSE;
break;
}
/*
* Move any trailing characters we didn't write in this go to the start
* of the buffer.
*/
if (cnt < linepos_)
{
size_t movecnt;
/* calculate how many trailing characters we didn't write */
movecnt = linepos_ - cnt;
/* move the characters, colors, and flags */
memmove(linebuf_, linebuf_ + cnt, movecnt * sizeof(linebuf_[0]));
memmove(colorbuf_, colorbuf_ + cnt, movecnt * sizeof(colorbuf_[0]));
memmove(flagbuf_, flagbuf_ + cnt, movecnt * sizeof(flagbuf_[0]));
}
/* move the line output position to follow the preserved characters */
linepos_ -= cnt;
/*
* If we just output a newline, note it. If we didn't just output a
* newline, but we did write out anything else, note that we're no
* longer at the start of a line on the underlying output device.
*/
if (nl == VM_NL_NEWLINE)
just_did_nl_ = TRUE;
else if (cnt != 0)
just_did_nl_ = FALSE;
/*
* if the current buffering color doesn't match the current osifc-layer
* color, then we must need to flush just the new color/attribute
* settings (this can happen when we have changed the attributes in
* preparation for reading input, since we won't have any actual text
* to write after the color change)
*/
if (!cur_color_.equals(&os_color_))
{
/* set the text attributes in the OS window, if they changed */
if (cur_color_.attr != os_color_.attr)
set_os_text_attr(cur_color_.attr);
/* set the color in the OS window, if it changed */
if (cur_color_.fg != os_color_.fg
|| cur_color_.bg != os_color_.bg)
set_os_text_color(cur_color_.fg, cur_color_.bg);
/* set the new osifc color */
os_color_ = cur_color_;
}
}
/* ------------------------------------------------------------------------ */
/*
* Clear out our buffers
*/
void CVmFormatter::empty_buffers(VMG0_)
{
/* reset our buffer pointers */
linepos_ = 0;
linecol_ = 0;
linebuf_[0] = '\0';
just_did_nl_ = FALSE;
is_continuation_ = FALSE;
/* there's no pending tab now */
pending_tab_align_ = VMFMT_TAB_NONE;
/* start out at the first line */
linecnt_ = 0;
/* reset the HTML lexical state */
html_passthru_state_ = VMCON_HPS_NORMAL;
}
/* ------------------------------------------------------------------------ */
/*
* Immediately update the display window
*/
void CVmFormatter::update_display(VMG0_)
{
/* update the display window at the OS layer */
os_update_display();
}
/* ------------------------------------------------------------------------ */
/*
* Display a blank line to the stream
*/
void CVmFormatter::write_blank_line(VMG0_)
{
/* flush the stream */
flush(vmg_ VM_NL_NEWLINE);
/* if generating for an HTML display target, add an HTML line break */
if (html_target_)
write_text(vmg_ L"<BR>", 4, 0, VM_NL_NONE);
/* write out a blank line */
write_text(vmg_ L"", 0, 0, VM_NL_NEWLINE);
}
/* ------------------------------------------------------------------------ */
/*
* Generate a tab for a "\t" sequence in the game text, or a <TAB
* MULTIPLE> or <TAB INDENT> sequence parsed in our mini-parser.
*
* Standard (non-HTML) version: we'll generate enough spaces to take us to
* the next tab stop.
*
* HTML version: if we're in native HTML mode, we'll just generate the
* equivalent HTML; if we're not in HTML mode, we'll generate a hard tab
* character, which the HTML formatter will interpret as a <TAB
* MULTIPLE=4>.
*/
void CVmFormatter::write_tab(VMG_ int indent, int multiple)
{
int maxcol;
/* check to see what the underlying system is expecting */
if (html_target_)
{
char buf[40];
/*
* the underlying system is HTML - generate an appropriate <TAB>
* sequence to produce the desired effect
*/
sprintf(buf, "<TAB %s=%d>",
indent != 0 ? "INDENT" : "MULTIPLE",
indent != 0 ? indent : multiple);
/* write it out */
buffer_string(vmg_ buf);
}
else if (multiple != 0)
{
/* get the maximum column */
maxcol = get_buffer_maxcol();
/*
* We don't have an HTML target, and we have a tab to an every-N
* stop: expand the tab with spaces. Keep going until we reach
* the next tab stop of the given multiple.
*/
do
{
/* stop if we've reached the maximum column */
if (linecol_ >= maxcol)
break;
/* add another space */
linebuf_[linepos_] = ' ';
flagbuf_[linepos_] = cur_flags_;
colorbuf_[linepos_] = cur_color_;
/* advance one character in the buffer */
++linepos_;
/* advance the column counter */
++linecol_;
} while ((linecol_ + 1) % multiple != 0);
}
else if (indent != 0)
{
/*
* We don't have an HTML target, and we just want to add a given
* number of spaces. Simply write out the given number of spaces,
* up to our maximum column limit.
*/
for (maxcol = get_buffer_maxcol() ;
indent != 0 && linecol_ < maxcol ; --indent)
{
/* add another space */
linebuf_[linepos_] = ' ';
flagbuf_[linepos_] = cur_flags_;
colorbuf_[linepos_] = cur_color_;
/* advance one character in the buffer and one column */
++linepos_;
++linecol_;
}
}
}
/* ------------------------------------------------------------------------ */
/*
* Flush a line
*/
void CVmFormatter::flush_line(VMG_ int padding)
{
/*
* check to see if we're using the underlying display layer's line
* wrapping
*/
if (os_line_wrap_)
{
/*
* In the HTML version, we don't need the normal *MORE*
* processing, since the HTML layer will handle that.
* Furthermore, we don't need to provide actual newline breaks
* -- that happens after the HTML is parsed, so we don't have
* enough information here to figure out actual line breaks.
* So, we'll just flush out our buffer whenever it fills up, and
* suppress newlines.
*
* Similarly, if we have OS-level line wrapping, don't try to
* figure out where the line breaks go -- just flush our buffer
* without a trailing newline whenever the buffer is full, and
* let the OS layer worry about formatting lines and paragraphs.
*
* If we're using padding, use newline mode VM_NL_OSNEWLINE. If
* we don't want padding (which is the case if we completely
* fill up the buffer without finding any word breaks), write
* out in mode VM_NL_NONE, which just flushes the buffer exactly
* like it is.
*/
flush(vmg_ padding ? VM_NL_OSNEWLINE : VM_NL_NONE_INTERNAL);
}
else
{
/*
* Normal mode - we process the *MORE* prompt ourselves, and we
* are responsible for figuring out where the actual line breaks
* go. Use flush() to generate an actual newline whenever we
* flush out our buffer.
*/
flush(vmg_ VM_NL_NEWLINE);
}
}
/* ------------------------------------------------------------------------ */
/*
* Write a character to an output stream. The character is provided to us
* as a wide Unicode character.
*/
void CVmFormatter::buffer_char(VMG_ wchar_t c)
{
const wchar_t *exp;
size_t exp_len;
/* check for a display expansion */
exp = (cmap_ != 0 ? cmap_ : G_cmap_to_ui)->get_expansion(c, &exp_len);
if (exp != 0)
{
/* write each character of the expansion */
for ( ; exp_len != 0 ; ++exp, --exp_len)
buffer_expchar(vmg_ *exp);
}
else
{
/* there's no expansion - buffer the character as-is */
buffer_expchar(vmg_ c);
}
}
/*
* Write an expanded character to an output stream.
*/
void CVmFormatter::buffer_expchar(VMG_ wchar_t c)
{
int i;
int cwid;
unsigned char cflags;
int shy;
int qspace;
/* presume the character takes up only one column */
cwid = 1;
/* presume we'll use the current flags for the new character */
cflags = cur_flags_;
/* assume it's not a quoted space */
qspace = FALSE;
/*
* Check for some special characters.
*
* If we have an underlying HTML renderer, keep track of the HTML
* lexical state, so we know if we're in a tag or in ordinary text. We
* can pass through all of the special line-breaking and spacing
* characters to the underlying HTML renderer.
*
* If our underlying renderer is a plain text renderer, we actually
* parse the HTML ourselves, so HTML tags will never make it this far -
* the caller will already have interpreted any HTML tags and removed
* them from the text stream, passing only the final plain text to us.
* However, with a plain text renderer, we have to do all of the work
* of line breaking, so we must look at the special spacing and
* line-break control characters.
*/
if (html_target_)
{
/*
* track the lexical state of the HTML stream going to the
* underlying renderer
*/
switch (html_passthru_state_)
{
case VMCON_HPS_MARKUP_END:
case VMCON_HPS_NORMAL:
/* check to see if we're starting a markup */
if (c == '&')
html_passthru_state_ = VMCON_HPS_ENTITY_1ST;
else if (c == '<')
html_passthru_state_ = VMCON_HPS_TAG;
else
html_passthru_state_ = VMCON_HPS_NORMAL;
break;
case VMCON_HPS_ENTITY_1ST:
/* check to see what kind of entity we have */
if (c == '#')
html_passthru_state_ = VMCON_HPS_ENTITY_NUM_1ST;
else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
html_passthru_state_ = VMCON_HPS_ENTITY_NAME;
else
html_passthru_state_ = VMCON_HPS_NORMAL;
break;
case VMCON_HPS_ENTITY_NUM_1ST:
/* check to see what kind of number we have */
if (c == 'x' || c == 'X')
html_passthru_state_ = VMCON_HPS_ENTITY_HEX;
else if (c >= '0' && c <= '9')
html_passthru_state_ = VMCON_HPS_ENTITY_DEC;
else
html_passthru_state_ = VMCON_HPS_NORMAL;
break;
case VMCON_HPS_ENTITY_HEX:
/* see if we're done with hex digits */
if (c == ';')
html_passthru_state_ = VMCON_HPS_MARKUP_END;
else if ((c < '0' || c > '9')
&& (c < 'a' || c > 'f')
&& (c < 'A' || c > 'F'))
html_passthru_state_ = VMCON_HPS_NORMAL;
break;
case VMCON_HPS_ENTITY_DEC:
/* see if we're done with decimal digits */
if (c == ';')
html_passthru_state_ = VMCON_HPS_MARKUP_END;
else if (c < '0' || c > '9')
html_passthru_state_ = VMCON_HPS_NORMAL;
break;
case VMCON_HPS_ENTITY_NAME:
/* see if we're done with alphanumerics */
if (c == ';')
html_passthru_state_ = VMCON_HPS_MARKUP_END;
else if ((c < 'a' || c > 'z')
&& (c < 'A' || c > 'Z')
&& (c < '0' || c > '9'))
html_passthru_state_ = VMCON_HPS_NORMAL;
break;
case VMCON_HPS_TAG:
/* see if we're done with the tag, or entering quoted material */
if (c == '>')
html_passthru_state_ = VMCON_HPS_MARKUP_END;
else if (c == '"')
html_passthru_state_ = VMCON_HPS_DQUOTE;
else if (c == '\'')
html_passthru_state_ = VMCON_HPS_SQUOTE;
break;
case VMCON_HPS_SQUOTE:
/* see if we're done with the quoted material */
if (c == '\'')
html_passthru_state_ = VMCON_HPS_TAG;
break;
case VMCON_HPS_DQUOTE:
/* see if we're done with the quoted material */
if (c == '"')
html_passthru_state_ = VMCON_HPS_TAG;
break;
default:
/* ignore other states */
break;
}
}
else
{
/* check for special characters */
switch(c)
{
case 0x00AD:
/*
* The Unicode "soft hyphen" character. This indicates a
* point at which we can insert a hyphen followed by a soft
* line break, if it's a convenient point to break the line;
* if we don't choose to break the line here, the soft hyphen
* is invisible.
*
* Don't buffer anything at all; instead, just flag the
* preceding character as being a soft hyphenation point, so
* that we can insert a hyphen there when we get around to
* breaking the line.
*/
if (linepos_ != 0)
flagbuf_[linepos_ - 1] |= VMCON_OBF_SHY;
/* we don't need to buffer anything, so we're done */
return;
case 0xFEFF:
/*
* The Unicode zero-width non-breaking space. This indicates
* a point at which we cannot break the line, even if we could
* normally break here. Flag the preceding character as a
* non-breaking point. Don't buffer anything for this
* character, as it's not rendered; it merely controls line
* breaking.
*/
if (linepos_ != 0)
flagbuf_[linepos_ - 1] |= VMCON_OBF_NOBREAK;
/* we don't buffer anything, so we're done */
return;
case 0x200B: /* zero-width space */
case 0x200a: /* hair space */
case 0x2008: /* punctuation space */
/*
* Zero-width space: This indicates an explicitly allowed
* line-breaking point, but is rendered as invisible. Flag the
* preceding character as an OK-to-break point, but don't
* buffer anything, as the zero-width space isn't rendered.
*
* Hair and punctuation spaces: Treat these very thin spaces as
* invisible in a fixed-width font. These are normally used
* for subtle typographical effects in proportionally-spaced
* fonts; for example, for separating a right single quote from
* an immediately following right double quote (as in a
* quotation within a quotation: I said, "type 'quote'"). When
* translating to fixed-pitch type, these special spacing
* effects aren't usually necessary or desirable because of the
* built-in space in every character cell.
*
* These spaces cancel any explicit non-breaking flag that
* precedes them, since they cause the flag to act on the
* space's left edge, while leaving the right edge open for
* breaking. Since we don't actually take up any buffer space,
* push our right edge's breakability back to the preceding
* character.
*/
if (linepos_ != 0)
{
flagbuf_[linepos_ - 1] &= ~VMCON_OBF_NOBREAK;
flagbuf_[linepos_ - 1] |= VMCON_OBF_OKBREAK;
}
/* we don't buffer anything, so we're done */
return;
case 0x00A0:
/* non-breaking space - buffer it as given */
break;
case 0x0015: /* special internal quoted space character */
case 0x2005: /* four-per-em space */
case 0x2006: /* six-per-em space */
case 0x2007: /* figure space */
case 0x2009: /* thin space */
/*
* Treat all of these as non-combining spaces, and render them
* all as single ordinary spaces. In text mode, we are
* limited to a monospaced font, so we can't render any
* differences among these various thinner-than-normal spaces.
*/
qspace = TRUE;
c = ' ';
break;
case 0x2002: /* en space */
case 0x2004: /* three-per-em space */
/*
* En space, three-per-em space - mark these as non-combining,
* and render them as a two ordinary spaces. In the case of
* an en space, we really do want to take up the space of two
* ordinary spaces; for a three-per-em space, we want about a
* space and a half, but since we're dealing with a monospaced
* font, we have to round up to a full two spaces.
*/
qspace = TRUE;
cwid = 2;
c = ' ';
break;
case 0x2003:
/* em space - mark it as non-combining */
qspace = TRUE;
/* render this as three ordinary spaces */
cwid = 3;
c = ' ';
break;
default:
/*
* Translate any whitespace character to a regular space
* character. Note that, once this is done, we don't need to
* worry about calling t3_is_space() any more - we can just
* check that we have a regular ' ' character.
*/
if (t3_is_space(c))
{
/* convert it to an ordinary space */
c = ' ';
/* if we're in obey-whitespace mode, quote this space */
qspace = obey_whitespace_;
}
break;
}
}
/* if it's a quoted space, mark it as such in the buffer flags */
if (qspace)
cflags |= VMCON_OBF_QSPACE;
/*
* Check for the caps/nocaps flags - but only if our HTML lexical state
* in the underlying text stream is plain text, because we don't want
* to apply these flags to alphabetic characters that are inside tag or
* entity text.
*/
if (html_passthru_state_ == VMCON_HPS_NORMAL)
{
if ((capsflag_ || allcapsflag_) && t3_is_alpha(c))
{
/* capsflag is set, so capitalize this character */
c = t3_to_upper(c);
/* okay, we've capitalized something; clear flag */
capsflag_ = FALSE;
}
else if (nocapsflag_ && t3_is_alpha(c))
{
/* nocapsflag is set, so minisculize this character */
c = t3_to_lower(c);
/* clear the flag now that we've done the job */
nocapsflag_ = FALSE;
}
}
/*
* If this is a space of some kind, we might be able to consolidate it
* with a preceding character.
*/
if (c == ' ')
{
/* ignore ordinary whitespace at the start of a line */
if (linecol_ == 0 && !qspace)
return;
/*
* Consolidate runs of whitespace. Ordinary whitespace is
* subsumed into any type of quoted spaces, but quoted spaces do
* not combine.
*/
if (linepos_ > 0)
{
wchar_t prv;
/* get the previous character */
prv = linebuf_[linepos_ - 1];
/*
* if the new character is an ordinary (combining) whitespace
* character, subsume it into any preceding space character
*/
if (!qspace && prv == ' ')
return;
/*
* if the new character is a quoted space, and the preceding
* character is a non-quoted space, subsume the preceding
* space into the new character
*/
if (qspace
&& prv == ' '
&& !(flagbuf_[linepos_ - 1] & VMCON_OBF_QSPACE))
{
/* remove the preceding ordinary whitespace */
--linepos_;
--linecol_;
}
}
}
/* if the new character fits in the line, add it */
if (linecol_ + cwid < get_buffer_maxcol())
{
/* buffer this character */
buffer_rendered(c, cflags, cwid);
/* we're finished processing the character */
return;
}
/*
* The line would overflow if this character were added.
*
* If we're trying to output any kind of breakable space, just add it
* to the line buffer for now; we'll come back later and figure out
* where to break upon buffering the next non-space character. This
* ensures that we don't carry trailing space (even trailing en or em
* spaces) to the start of the next line if we have an explicit
* newline before the next non-space character.
*/
if (c == ' ')
{
/*
* We're adding a space, so we'll figure out the breaking later,
* when we output the next non-space character. If the preceding
* character is any kind of space, don't bother adding the new
* one, since any contiguous whitespace at the end of the line has
* no effect on the line's appearance.
*/
if (linebuf_[linepos_ - 1] == ' ')
{
/*
* We're adding a space to a line that already ends in a
* space, so we don't really need to add the character.
* However, reflect the virtual addition in the output column
* position, since the space does affect our column position.
* We know that we're adding the new space even though we have
* a space preceding, since we wouldn't have gotten this far
* if we were going to collapse the space with a run of
* whitespace.
*/
}
else
{
/* the line doesn't already end in space, so add the space */
linebuf_[linepos_] = ' ';
flagbuf_[linepos_] = cflags;
colorbuf_[linepos_] = cur_color_;
/* advance one character in the buffer */
++linepos_;
}
/*
* Adjust the column position for the added space. Note that we
* adjust by the rendered width of the new character even though
* we actually added only one character; we only add one character
* to the buffer to avoid buffer overflow, but the column position
* needs adjustment by the full rendered width. The fact that the
* actual buffer size and rendered width no longer match isn't
* important because the difference is entirely in invisible
* whitespace at the right end of the line.
*/
linecol_ += cwid;
/* done for now */
return;
}
/*
* We're adding something other than an ordinary space to the line,
* and the new character won't fit, so we must find an appropriate
* point to break the line.
*
* First, add the new character to the buffer - it could be
* significant in how we calculate the break position. (Note that we
* allocate the buffer with space for one extra character after
* reaching the maximum line width, so we know we have room for this.)
*/
linebuf_[linepos_] = c;
flagbuf_[linepos_] = cur_flags_;
/*
* if the underlying OS layer is doing the line wrapping, just flush
* out the buffer; don't bother trying to do any line wrapping
* ourselves, since this work would just be redundant with what the OS
* layer has to do anyway
*/
if (os_line_wrap_)
{
/* flush the line, adding no padding after it */
flush_line(vmg_ FALSE);
/*
* we've completely cleared out the line buffer, so reset all of
* the line buffer counters
*/
linepos_ = 0;
linecol_ = 0;
linebuf_[0] = '\0';
is_continuation_ = FALSE;
/* we're done */
goto done_with_wrapping;
}
/*
* Scan backwards, looking for a break position. Start at the current
* column: we know we can fit everything up to this point on a line on
* the underlying display, so this is the rightmost possible position
* at which we could break the line. Keep going until we find a
* breaking point or reach the left edge of the line.
*/
for (shy = FALSE, i = linepos_ ; i >= 0 ; --i)
{
unsigned char f;
unsigned char prvf;
/*
* There are two break modes: word-break mode and break-anywhere
* mode. The modes are applied to each character, via the buffer
* flags.
*
* In word-break mode, we can break at any ordinary space, at a
* soft hyphen, just after a regular hyphen, or at any explicit
* ok-to-break point; but we can't break after any character
* marked as a no-break point.
*
* In break-anywhere mode, we can break between any two
* characters, except that we can't break after any character
* marked as a no-break point.
*/
/* get the current character's flags */
f = flagbuf_[i];
/* get the preceding character's flags */
prvf = (i > 0 ? flagbuf_[i-1] : 0);
/*
* if the preceding character is marked as a no-break point, we
* definitely can't break here, so keep looking
*/
if ((prvf & VMCON_OBF_NOBREAK) != 0)
continue;
/*
* if the preceding character is marked as an explicit ok-to-break
* point, we definitely can break here
*/
if ((prvf & VMCON_OBF_OKBREAK) != 0)
break;
/*
* If the current character is in a run of break-anywhere text,
* then we can insert a break just before the current character.
* Likewise, if the preceding character is in a run of
* break-anywhere text, we can break just after the preceding
* character, which is the same as breaking just before the
* current character.
*
* Note that we must test for both cases to properly handle
* boundaries between break-anywhere and word-break text. If
* we're switching from word-break to break-anywhere text, the
* current character will be marked as break-anywhere, so if we
* only tested the previous character, we'd miss this transition.
* If we're switching from break-anywhere to word-break text, the
* previous character will be marked as break-anywhere, so we'd
* miss the fact that we could break right here (rather than
* before the previous character) if we didn't test it explicitly.
*/
if ((f & VMCON_OBF_BREAK_ANY) != 0
|| (i > 0 && (prvf & VMCON_OBF_BREAK_ANY) != 0))
break;
/*
* If the preceding character is marked as a soft hyphenation
* point, and we're not at the rightmost position, we can break
* here with hyphenation. We can't break with hyphenation at the
* last position because hyphenation requires us to actually
* insert a hyphen character, and we know that at the last
* position we don't have room for inserting another character.
*/
if (i > 0 && i < linepos_ && (prvf & VMCON_OBF_SHY) != 0)
{
/* note that we're breaking at a soft hyphen */
shy = TRUE;
/* we can break here */
break;
}
/*
* we can break to the left of a space (i.e., we can break before
* the current character if the current character is a space)
*/
if (linebuf_[i] == ' ')
break;
/*
* We can also break to the right of a space. We need to check
* for this case separately from checking that the current
* charatcer is a space (which breaks to the left of the space),
* because we could have a no-break marker on one side of the
* space but not on the other side.
*/
if (i > 0 && linebuf_[i-1] == ' ')
break;
/*
* If we're to the right of a hyphen, we can break here. However,
* don't break in the middle of a set of consecutive hyphens
* (i.e., we don't want to break up "--" sequences).
*/
if (i > 0 && linebuf_[i-1] == '-' && linebuf_[i] != '-')
break;
}
/* check to see if we found a good place to break */
if (i < 0)
{
/*
* We didn't find a good place to break. If the underlying
* console allows overrunning the line width, simply add the
* character, even though it overflows; otherwise, force a break
* at the line width, even though it doesn't occur at a natural
* breaking point.
*
* In any case, don't let our buffer fill up beyond its maximum
* size.
*/
if (!console_->allow_overrun() || linepos_ + 1 >= OS_MAXWIDTH)
{
/*
* we didn't find any good place to break, and the console
* doesn't allow us to overrun the terminal width - flush the
* entire line as-is, breaking arbitrarily in the middle of a
* word
*/
flush_line(vmg_ FALSE);
/*
* we've completely cleared out the line buffer, so reset all
* of the line buffer counters
*/
linepos_ = 0;
linecol_ = 0;
linebuf_[0] = '\0';
is_continuation_ = FALSE;
}
}
else
{
wchar_t tmpbuf[OS_MAXWIDTH];
vmcon_color_t tmpcolor[OS_MAXWIDTH];
unsigned char tmpflags[OS_MAXWIDTH];
size_t tmpchars;
int nxti;
/* null-terminate the line buffer */
linebuf_[linepos_] = '\0';
/* trim off leading spaces on the next line after the break */
for (nxti = i ; linebuf_[nxti] == ' ' ; ++nxti) ;
/*
* The next line starts after the break - save a copy. We actually
* have to save a copy of the trailing material outside the buffer,
* since we might have to overwrite the trailing part of the buffer
* to expand tabs.
*/
tmpchars = wcslen(&linebuf_[nxti]);
memcpy(tmpbuf, &linebuf_[nxti], tmpchars*sizeof(tmpbuf[0]));
memcpy(tmpcolor, &colorbuf_[nxti], tmpchars*sizeof(tmpcolor[0]));
memcpy(tmpflags, &flagbuf_[nxti], tmpchars*sizeof(tmpflags[0]));
/* if we're breaking at a soft hyphen, insert a real hyphen */
if (shy)
linebuf_[i++] = '-';
/* trim off trailing spaces */
for ( ; i > 0 && linebuf_[i-1] == ' ' ; --i)
{
/* stop if we've reached a non-breaking point */
if ((flagbuf_[i-1] & VMCON_OBF_NOBREAK) != 0)
break;
}
/* terminate the buffer after the break point */
linebuf_[i] = '\0';
/* write out everything up to the break point */
flush_line(vmg_ TRUE);
/* move the saved start of the next line into the line buffer */
memcpy(linebuf_, tmpbuf, tmpchars*sizeof(tmpbuf[0]));
memcpy(colorbuf_, tmpcolor, tmpchars*sizeof(tmpcolor[0]));
memcpy(flagbuf_, tmpflags, tmpchars*sizeof(tmpflags[0]));
linecol_ = linepos_ = tmpchars;
}
done_with_wrapping:
/* add the new character to buffer */
buffer_rendered(c, cflags, cwid);
}
/*
* Write a rendered character to an output stream buffer. This is a
* low-level internal routine that we call from buffer_expchar() to put
* the final rendition of a character into a buffer.
*
* Some characters render as multiple copies of a single character; 'wid'
* gives the number of copies to store. The caller is responsible for
* ensuring that the rendered representation fits in the buffer and in the
* available line width.
*/
void CVmFormatter::buffer_rendered(wchar_t c, unsigned char flags, int wid)
{
unsigned char flags_before;
/* note whether or not we have a break before us */
flags_before = (linepos_ > 0
? flagbuf_[linepos_-1] & VMCON_OBF_NOBREAK
: 0);
/* add the character the given number of times */
for ( ; wid != 0 ; --wid)
{
/* buffer the character */
linebuf_[linepos_] = c;
flagbuf_[linepos_] = flags;
colorbuf_[linepos_] = cur_color_;
/*
* if this isn't the last part of the character, carry forward any
* no-break flag from the previous part of the character; this will
* ensure that a no-break to the left of the sequence applies to
* the entire sequence
*/
if (wid > 1)
flagbuf_[linepos_] |= flags_before;
/* advance one character in the buffer */
++linepos_;
/* adjust our column counter */
++linecol_;
}
}
/* ------------------------------------------------------------------------ */
/*
* write out a UTF-8 string
*/
void CVmFormatter::buffer_string(VMG_ const char *txt)
{
/* write out each character in the string */
for ( ; utf8_ptr::s_getch(txt) != 0 ; txt += utf8_ptr::s_charsize(*txt))
buffer_char(vmg_ utf8_ptr::s_getch(txt));
}
/*
* write out a wide unicode string
*/
void CVmFormatter::buffer_wstring(VMG_ const wchar_t *txt)
{
/* write out each wide character */
for ( ; *txt != '\0' ; ++txt)
buffer_char(vmg_ *txt);
}
/* ------------------------------------------------------------------------ */
/*
* Get the next wide unicode character in a UTF8-encoded string, and
* update the string pointer and remaining length. Returns zero if no
* more characters are available in the string.
*/
wchar_t CVmFormatter::next_wchar(const char **s, size_t *len)
{
wchar_t ret;
size_t charsize;
/* if there's nothing left, return a null terminator */
if (*len == 0)
return 0;
/* get this character */
ret = utf8_ptr::s_getch(*s);
/* advance the string pointer and length counter */
charsize = utf8_ptr::s_charsize(**s);
*len -= charsize;
*s += charsize;
/* return the result */
return ret;
}
/* ------------------------------------------------------------------------ */
/*
* Display a string of a given length. The text is encoded as UTF-8
* characters.
*/
int CVmFormatter::format_text(VMG_ const char *s, size_t slen)
{
wchar_t c;
int done = FALSE;
/* get the first character */
c = next_wchar(&s, &slen);
/* if we have anything to show, show it */
while (c != '\0')
{
/*
* first, process the character through our built-in text-only HTML
* mini-parser, if our HTML mini-parser state indicates that we're
* in the midst of parsing a tag
*/
if (html_parse_state_ != VMCON_HPS_NORMAL
|| (html_in_ignore_ && c != '&' && c != '<'))
{
/* run our HTML parsing until we finish the tag */
c = resume_html_parsing(vmg_ c, &s, &slen);
/* proceed with the next character */
continue;
}
/* check for special characters */
switch(c)
{
case 10:
/* newline */
flush(vmg_ VM_NL_NEWLINE);
break;
case 9:
/* tab - write an ordinary every-4-columns tab */
write_tab(vmg_ 0, 4);
break;
case 0x000B:
/* \b - blank line */
write_blank_line(vmg0_);
break;
case 0x000F:
/* capitalize next character */
capsflag_ = TRUE;
nocapsflag_ = FALSE;
break;
case 0x000E:
/* un-capitalize next character */
nocapsflag_ = TRUE;
capsflag_ = FALSE;
break;
case '<':
case '&':
/* HTML markup-start character - process it */
if (html_target_ || literal_mode_)
{
/*
* The underlying OS renderer interprets HTML mark-up
* sequences, OR we're processing all text literally; in
* either case, we don't need to perform any
* interpretation. Simply pass through the character as
* though it were any other.
*/
goto normal_char;
}
else
{
/*
* The underlying target does not accept HTML sequences.
* It appears we're at the start of an "&" entity or a tag
* sequence, so parse it, remove it, and replace it (if
* possible) with a text-only equivalent.
*/
c = parse_html_markup(vmg_ c, &s, &slen);
/* go back and process the next character */
continue;
}
break;
case 0x0015: /* our own quoted space character */
case 0x00A0: /* non-breaking space */
case 0x00AD: /* soft hyphen */
case 0xFEFF: /* non-breaking zero-width space */
case 0x2002: /* en space */
case 0x2003: /* em space */
case 0x2004: /* three-per-em space */
case 0x2005: /* four-per-em space */
case 0x2006: /* six-per-em space */
case 0x2007: /* figure space */
case 0x2008: /* punctuation space */
case 0x2009: /* thin space */
case 0x200a: /* hair space */
case 0x200b: /* zero-width space */
/*
* Special Unicode characters. For HTML targets, write these
* as &# sequences - this bypasses character set translation
* and ensures that the HTML parser will see them as intended.
*/
if (html_target_)
{
char buf[15];
char *p;
/*
* it's an HTML target - render these as &# sequences;
* generate the decimal representation of 'c' (in reverse
* order, hence start with the terminating null byte and
* the semicolon)
*/
p = buf + sizeof(buf) - 1;
*p-- = '\0';
*p-- = ';';
/* generate the decimal representation of 'c' */
for ( ; c != 0 ; c /= 10)
*p-- = (c % 10) + '0';
/* add the '&#' sequence */
*p-- = '#';
*p = '&';
/* write out the sequence */
buffer_string(vmg_ p);
}
else
{
/* for non-HTML targets, treat these as normal */
goto normal_char;
}
break;
default:
normal_char:
/* normal character - write it out */
buffer_char(vmg_ c);
break;
}
/* move on to the next character, unless we're finished */
if (done)
c = '\0';
else
c = next_wchar(&s, &slen);
}
/* success */
return 0;
}
/* ------------------------------------------------------------------------ */
/*
* Initialize the display object
*/
CVmConsole::CVmConsole()
{
/* no script file yet */
script_sp_ = 0;
/* no command log file yet */
command_fp_ = 0;
/* assume we'll double-space after each period */
doublespace_ = TRUE;
/* presume we'll have no log stream */
log_str_ = 0;
log_enabled_ = FALSE;
}
/*
* Delete the display object
*/
CVmConsole::~CVmConsole()
{
/* close any active script file(s) */
while (script_sp_ != 0)
{
/* close this file */
osfcls(script_sp_->fp);
/* unlink this stack level */
script_stack_entry *cur = script_sp_;
script_sp_ = cur->enc;
/* delete the entry */
delete cur;
}
/* close any active command log file */
close_command_log();
/* delete the log stream if we have one */
if (log_str_ != 0)
delete log_str_;
}
/* ------------------------------------------------------------------------ */
/*
* Display a string of a given byte length
*/
int CVmConsole::format_text(VMG_ const char *p, size_t len)
{
/* display the string */
disp_str_->format_text(vmg_ p, len);
/* if there's a log file, write to the log file as well */
if (log_enabled_)
log_str_->format_text(vmg_ p, len);
/* indicate success */
return 0;
}
/*
* Display a string on the log stream only
*/
int CVmConsole::format_text_to_log(VMG_ const char *p, size_t len)
{
/* if there's a log file, write to it; otherwise ignore the whole thing */
if (log_enabled_)
log_str_->format_text(vmg_ p, len);
/* indicate success */
return 0;
}
/* ------------------------------------------------------------------------ */
/*
* Set the text color
*/
void CVmConsole::set_text_color(VMG_ os_color_t fg, os_color_t bg)
{
/* set the color in our main display stream */
disp_str_->set_text_color(vmg_ fg, bg);
}
/*
* Set the body color
*/
void CVmConsole::set_body_color(VMG_ os_color_t color)
{
/* set the color in the main display stream */
disp_str_->set_os_body_color(color);
}
/* ------------------------------------------------------------------------ */
/*
* Display a blank line
*/
void CVmConsole::write_blank_line(VMG0_)
{
/* generate the newline to the standard display */
disp_str_->write_blank_line(vmg0_);
/* if we're logging, generate the newline to the log file as well */
if (log_enabled_)
log_str_->write_blank_line(vmg0_);
}
/* ------------------------------------------------------------------------ */
/*
* outcaps() - sets an internal flag which makes the next letter output
* a capital, whether it came in that way or not. Set the same state in
* both formatters (standard and log).
*/
void CVmConsole::caps()
{
disp_str_->caps();
if (log_enabled_)
log_str_->caps();
}
/*
* outnocaps() - sets the next letter to a miniscule, whether it came in
* that way or not.
*/
void CVmConsole::nocaps()
{
disp_str_->nocaps();
if (log_enabled_)
log_str_->nocaps();
}
/*
* obey_whitespace() - sets the obey-whitespace mode
*/
int CVmConsole::set_obey_whitespace(int f)
{
int ret;
/* note the original display stream status */
ret = disp_str_->get_obey_whitespace();
/* set the stream status */
disp_str_->set_obey_whitespace(f);
if (log_enabled_)
log_str_->set_obey_whitespace(f);
/* return the original status of the display stream */
return ret;
}
/* ------------------------------------------------------------------------ */
/*
* Open a log file
*/
int CVmConsole::open_log_file(const char *fname)
{
/* if there's no log stream, we can't open a log file */
if (log_str_ == 0)
return 1;
/*
* Tell the log stream to open the file. Set the log file's HTML
* source mode flag to the same value as is currently being used in
* the main display stream, so that it will interpret source markups
* the same way that the display stream is going to.
*/
return log_str_->open_log_file(fname);
}
/*
* Close the log file
*/
int CVmConsole::close_log_file()
{
/* if there's no log stream, there's obviously no file open */
if (log_str_ == 0)
return 1;
/* tell the log stream to close its file */
return log_str_->close_log_file();
}
#if 0 //$$$
/*
* This code is currently unused. However, I'm leaving it in for now -
* the algorithm takes a little thought, so it would be nicer to be able
* to uncomment the existing code should we ever need it in the future.
*/
/* ------------------------------------------------------------------------ */
/*
* Write UTF-8 text explicitly to the log file. This can be used to add
* special text (such as prompt text) that would normally be suppressed
* from the log file. When more mode is turned off, we don't
* automatically copy text to the log file; any text that the caller
* knows should be in the log file during times when more mode is turned
* off can be explicitly added with this function.
*
* If nl is true, we'll add a newline at the end of this text. The
* caller should not include any newlines in the text being displayed
* here.
*/
void CVmConsole::write_to_logfile(VMG_ const char *txt, int nl)
{
/* if there's no log file, there's nothing to do */
if (logfp_ == 0)
return;
/* write the text in the log file character set */
write_to_file(logfp_, txt, G_cmap_to_log);
/* add a newline if desired */
if (nl)
{
/* add a normal newline */
os_fprintz(logfp_, "\n");
/* if the logfile is an html target, write an HTML line break */
if (log_str_ != 0 && log_str_->is_html_target())
os_fprintz(logfp_, "<BR HEIGHT=0>\n");
}
/* flush the output */
osfflush(logfp_);
}
/*
* Write text to a file in the given character set
*/
void CVmConsole::write_to_file(osfildef *fp, const char *txt,
CCharmapToLocal *map)
{
size_t txtlen = strlen(txt);
/*
* convert the text from UTF-8 to the local character set and write the
* converted text to the log file
*/
while (txtlen != 0)
{
char local_buf[128];
size_t src_bytes_used;
size_t out_bytes;
/* convert as much as we can (leaving room for a null terminator) */
out_bytes = map->map_utf8(local_buf, sizeof(local_buf),
txt, txtlen, &src_bytes_used);
/* null-terminate the result */
local_buf[out_bytes] = '\0';
/* write the converted text */
os_fprintz(fp, local_buf);
/* skip the text we were able to convert */
txt += src_bytes_used;
txtlen -= src_bytes_used;
}
}
#endif /* 0 */
/* ------------------------------------------------------------------------ */
/*
* Reset the MORE line counter. This should be called whenever user
* input is read, since stopping to read user input makes it unnecessary
* to show another MORE prompt until the point at which input was
* solicited scrolls off the screen.
*/
void CVmConsole::reset_line_count(int clearing)
{
/* reset the MORE counter in the display stream */
disp_str_->reset_line_count(clearing);
}
/* ------------------------------------------------------------------------ */
/*
* Flush the output line. We'll write to both the standard display and
* the log file, as needed.
*/
void CVmConsole::flush(VMG_ vm_nl_type nl)
{
/* flush the display stream */
disp_str_->flush(vmg_ nl);
/* flush the log stream, if we have an open log file */
if (log_enabled_)
log_str_->flush(vmg_ nl);
}
/* ------------------------------------------------------------------------ */
/*
* Clear our buffers
*/
void CVmConsole::empty_buffers(VMG0_)
{
/* tell the formatter to clear its buffer */
disp_str_->empty_buffers(vmg0_);
/* same with the log stream, if applicable */
if (log_enabled_)
log_str_->empty_buffers(vmg0_);
}
/* ------------------------------------------------------------------------ */
/*
* Immediately update the display
*/
void CVmConsole::update_display(VMG0_)
{
/* update the display for the main display stream */
disp_str_->update_display(vmg0_);
}
/* ------------------------------------------------------------------------ */
/*
* Open a script file
*/
void CVmConsole::open_script_file(const char *fname, int quiet,
int script_more_mode)
{
int evt;
char buf[50];
/* try opening the file */
osfildef *fp = osfoprt(fname, OSFTCMD);
/* if that failed, silently ignore the request */
if (fp == 0)
return;
/* read the first line to see if it looks like an event script */
if (osfgets(buf, sizeof(buf), fp) != 0
&& strcmp(buf, "<eventscript>\n") == 0)
{
/* remember that it's an event script */
evt = TRUE;
}
else
{
/*
* it's not an event script, so it must be a regular command-line
* script - rewind it so we read the first line again as a regular
* input line
*/
evt = FALSE;
osfseek(fp, 0, OSFSK_SET);
}
/* if there's an enclosing script, inherit its modes */
if (script_sp_ != 0)
{
/*
* if the enclosing script is quiet, force the nested script to be
* quiet as well
*/
if (script_sp_->quiet)
quiet = TRUE;
/*
* if the enclosing script is nonstop, force the nested script to
* be nonstop as well
*/
if (!script_sp_->more_mode)
script_more_mode = FALSE;
}
/* push the new script file onto the stack */
script_sp_ = new script_stack_entry(
script_sp_, set_more_state(script_more_mode), fp,
script_more_mode, quiet, evt);
/* turn on NONSTOP mode in the OS layer if applicable */
if (!script_more_mode)
os_nonstop_mode(TRUE);
}
/*
* Close the current script file
*/
int CVmConsole::close_script_file()
{
script_stack_entry *e;
/* if we have a file, close it */
if ((e = script_sp_) != 0)
{
int ret;
/* close the file */
osfcls(e->fp);
/* pop the stack */
script_sp_ = e->enc;
/* restore the enclosing level's MORE mode */
os_nonstop_mode(!e->old_more_mode);
/*
* return the MORE mode in effect before we started reading the
* script file
*/
ret = e->old_more_mode;
/* delete the stack level */
delete e;
/* return the result */
return ret;
}
else
{
/*
* there's no script file - just return the current MORE mode,
* since we're not making any changes
*/
return is_more_mode();
}
}
/* ------------------------------------------------------------------------ */
/*
* Open a command log file
*/
int CVmConsole::open_command_log(const char *fname, int event_script)
{
/* close any existing command log file */
close_command_log();
/* remember the filename */
strcpy(command_fname_, fname);
/* open the file */
command_fp_ = osfopwt(fname, OSFTCMD);
/* note the type */
command_eventscript_ = event_script;
/* if it's an event script, write the file type tag */
if (event_script && command_fp_ != 0)
{
os_fprintz(command_fp_, "<eventscript>\n");
osfflush(command_fp_);
}
/* return success if we successfully opened the file */
return (command_fp_ == 0);
}
/*
* close the active command log file
*/
int CVmConsole::close_command_log()
{
/* if there's a command log file, close it */
if (command_fp_ != 0)
{
/* close the file */
osfcls(command_fp_);
/* set its file type */
os_settype(command_fname_, OSFTCMD);
/* forget the file */
command_fp_ = 0;
}
/* success */
return 0;
}
/* ------------------------------------------------------------------------ */
/*
* Read a line of input from the console. Fills in the buffer with a
* null-terminated string in the UTF-8 character set. Returns zero on
* success, non-zero on end-of-file.
*/
int CVmConsole::read_line(VMG_ char *buf, size_t buflen)
{
/* cancel any previous interrupted input */
read_line_cancel(vmg_ TRUE);
try_again:
/* use the timeout version, with no timeout specified */
switch(read_line_timeout(vmg_ buf, buflen, 0, FALSE))
{
case OS_EVT_LINE:
/* success */
return 0;
case VMCON_EVT_END_SCRIPT:
/*
* end of script - we have no way to communicate this result back
* to our caller, so simply ignore the result and ask for another
* line
*/
goto try_again;
default:
/* anything else is an error */
return 1;
}
}
/* ------------------------------------------------------------------------ */
/*
* Log an event to the output script. The parameter is in the UI character
* set.
*/
int CVmConsole::log_event(VMG_ int evt,
const char *param, size_t paramlen,
int param_is_utf8)
{
/* if there's a script file, log the event */
if (command_fp_ != 0)
{
/* write the event in the proper format for the script type */
if (command_eventscript_)
{
const char *tag = 0;
/* write the event according to its type */
switch (evt)
{
case OS_EVT_KEY:
/* use the "<key>" tag */
tag = "<key>";
/*
* use the normal key representation, except we want to
* write \n as [enter] and \t as [tab]
*/
if (param != 0)
{
switch (*param)
{
case '\n':
param = "[enter]";
paramlen = 7;
break;
case '\t':
param = "[tab]";
paramlen = 5;
break;
case ' ':
param = "[space]";
paramlen = 7;
break;
}
}
break;
case OS_EVT_TIMEOUT:
tag = "<timeout>";
break;
case OS_EVT_HREF:
tag = "<href>";
break;
case OS_EVT_NOTIMEOUT:
tag = "<notimeout>";
param = 0;
break;
case OS_EVT_EOF:
tag = "<eof>";
param = 0;
break;
case OS_EVT_LINE:
tag = "<line>";
break;
case OS_EVT_COMMAND:
tag = "<command>";
break;
case VMCON_EVT_END_SCRIPT:
tag = "<endqs>";
break;
case VMCON_EVT_DIALOG:
tag = "<dialog>";
break;
case VMCON_EVT_FILE:
tag = "<file>";
break;
}
/* if we found a tag, write it */
if (tag != 0)
{
/* write the tag, in the local character set */
G_cmap_to_ui->write_file(command_fp_, tag, strlen(tag));
/* add the parameter, if present */
if (param != 0)
{
if (param_is_utf8)
G_cmap_to_ui->write_file(
command_fp_, param, paramlen);
else
os_fprint(command_fp_, param, paramlen);
}
/* add the newline */
G_cmap_to_ui->write_file(command_fp_, "\n", 1);
/* flush the output */
osfflush(command_fp_);
}
}
else
{
/*
* It's a plain old command-line script. If the event is an
* input-line event, record it; otherwise leave it out, as this
* script file format can't represent any other event types.
*/
if (evt == OS_EVT_LINE && param != 0)
{
/* write the ">" prefix */
G_cmap_to_ui->write_file(command_fp_, ">", 1);
/* add the command line */
if (param_is_utf8)
G_cmap_to_ui->write_file(command_fp_, param, paramlen);
else
os_fprint(command_fp_, param, paramlen);
/* add the newline */
G_cmap_to_ui->write_file(command_fp_, "\n", 1);
/* flush the output */
osfflush(command_fp_);
}
}
}
/* return the event code */
return evt;
}
/* ------------------------------------------------------------------------ */
/*
* Static variables for input state. We keep these statically, because we
* might need to use the values across a series of read_line_timeout calls
* if timeouts occur.
*/
/* original 'more' mode, before input began */
static int S_old_more_mode;
/* flag: input is pending from an interrupted read_line_timeout invocation */
static int S_read_in_progress;
/* local buffer for reading input lines */
static char S_read_buf[256];
/*
* Read a line of input from the console, with an optional timeout value.
*/
int CVmConsole::read_line_timeout(VMG_ char *buf, size_t buflen,
unsigned long timeout, int use_timeout)
{
int echo_text;
char *outp;
size_t outlen;
int evt;
int resuming;
/*
* presume we won't echo the text to the display; in most cases, it
* will be echoed to the display in the course of reading it from
* the keyboard
*/
echo_text = FALSE;
/* remember the initial MORE mode */
S_old_more_mode = is_more_mode();
/*
* If we're not resuming an interrupted read already in progress,
* initialize some display settings.
*/
if (!S_read_in_progress)
{
/*
* Turn off MORE mode if it's on - we don't want a MORE prompt
* showing up in the midst of user input.
*/
S_old_more_mode = set_more_state(FALSE);
/*
* flush the output; don't start a new line, since we might have
* displayed a prompt that is to be on the same line with the user
* input
*/
flush_all(vmg_ VM_NL_INPUT);
/* if there's a script file, read from it */
if (script_sp_ != 0)
{
read_script:
/* note whether we're in quiet mode */
int was_quiet = script_sp_->quiet;
/* try reading a line from the script file */
if (read_line_from_script(S_read_buf, sizeof(S_read_buf), &evt))
{
/*
* we successfully got a line from the script file - if
* we're not in quiet mode, make a note to echo the text to
* the display
*/
if (!script_sp_->quiet)
echo_text = TRUE;
}
else
{
int is_quiet;
/*
* End of script file - return to reading from the
* enclosing level (i.e., the enclosing script, or the
* keyboard if this is the outermost script). The return
* value from close_script_file() is the MORE mode that was
* in effect before we started reading the script file;
* we'll use this when we restore the enclosing MORE mode
* so that we restore the pre-script MORE mode when we
* return.
*/
S_old_more_mode = close_script_file();
/* note the new 'quiet' mode */
is_quiet = (script_sp_ != 0 && script_sp_->quiet);
/*
* if we're still reading from a script (which means we
* closed the old script and popped out to an enclosing
* script), and the 'quiet' mode hasn't changed, simply go
* back for another read
*/
if (script_sp_ != 0 && is_quiet == was_quiet)
goto read_script;
/*
* temporarily turn off MORE mode, in case we read from the
* keyboard
*/
set_more_state(FALSE);
/* flush any output we generated while reading the script */
flush(vmg_ VM_NL_NONE);
/*
* If we were in quiet mode but no longer are, let the
* caller know we've finished reading a script, so that the
* caller can set up the display properly for reading from
* the keyboard.
*
* If we weren't in quiet mode, we'll simply proceed to the
* normal keyboard reading; when not in quiet mode, no
* special display fixup is needed.
*/
if (was_quiet && !is_quiet)
{
/* return to the old MORE mode */
set_more_state(S_old_more_mode);
/* add a blank line to the log file, if necessary */
if (log_enabled_)
log_str_->print_to_os("\n");
/* note in the streams that we've read an input line */
disp_str_->note_input_line();
if (log_str_ != 0)
log_str_->note_input_line();
/*
* generate a synthetic "end of script" event to let
* the caller know we're switching back to regular
* keyboard reading
*/
return log_event(vmg_ VMCON_EVT_END_SCRIPT);
}
/*
* Note that we do not have an event yet - we've merely
* closed the script file, and now we're going to continue
* by reading a line from the keyboard instead. The call
* to close_script_file() above will have left script_sp_
* == 0, so we'll shortly read an event from the keyboard.
* Thus 'evt' is still not set to any value, because we do
* not yet have an event - this is intentional.
*/
}
}
/*
* if we're not reading from a scripot, reset the MORE line
* counter, since we're reading user input at the current point and
* shouldn't pause for a MORE prompt until the text we're reading
* has scrolled off the screen
*/
if (script_sp_ == 0)
reset_line_count(FALSE);
}
/*
* if reading was already in progress, we're resuming a previously
* interrupted read operation
*/
resuming = S_read_in_progress;
/* reading is now in progress */
S_read_in_progress = TRUE;
/*
* if we don't have a script file, or we're resuming an interrupted
* read operation, read from the keyboard
*/
if (script_sp_ == 0 || resuming)
{
/* read a line from the keyboard */
evt = os_gets_timeout((uchar *)S_read_buf, sizeof(S_read_buf),
timeout, use_timeout);
/*
* If that failed because timeout is not supported on this
* platform, and the caller didn't actually want to use a timeout,
* try again with an ordinary os_gets(). If they wanted to use a
* timeout, simply return the NOTIMEOUT indication to our caller.
*/
if (evt == OS_EVT_NOTIMEOUT && !use_timeout)
{
/* perform an ordinary untimed input */
if (os_gets((uchar *)S_read_buf, sizeof(S_read_buf)) != 0)
{
/* success */
evt = OS_EVT_LINE;
}
else
{
/* error reading input */
evt = OS_EVT_EOF;
}
}
/*
* If we actually read a line, notify the display stream that we
* read text from the console - it might need to make some
* internal bookkeeping adjustments to account for the fact that
* we moved the write position around on the display.
*
* Don't note the input if we timed out, since we haven't finished
* reading the line yet in this case.
*/
if (evt == OS_EVT_LINE)
{
disp_str_->note_input_line();
if (log_str_ != 0)
log_str_->note_input_line();
}
}
/* if we got an error, return it */
if (evt == OS_EVT_EOF)
{
set_more_state(S_old_more_mode);
return log_event(vmg_ evt);
}
/*
* Convert the text from the local UI character set to UTF-8. Reserve
* space in the output buffer for the null terminator.
*/
outp = buf;
outlen = buflen - 1;
G_cmap_from_ui->map(&outp, &outlen, S_read_buf, strlen(S_read_buf));
/* add the null terminator */
*outp = '\0';
/*
* If we need to echo the text (because we read it from a script file),
* do so now.
*/
if (echo_text)
{
/* show the text */
format_text(vmg_ buf);
/* add a newline */
format_text(vmg_ "\n");
}
/* if we finished reading the line, do our line-finishing work */
if (evt == OS_EVT_LINE)
read_line_done(vmg0_);
/*
* Log and return the event. Note that we log events in the UI
* character set, so we want to simply use the original, untranslated
* input buffer.
*/
return log_event(vmg_ evt, S_read_buf, strlen(S_read_buf), FALSE);
}
/*
* Cancel an interrupted input.
*/
void CVmConsole::read_line_cancel(VMG_ int reset)
{
/* reset the underling OS layer */
os_gets_cancel(reset);
/* do our line-ending work */
read_line_done(vmg0_);
}
/*
* Perform line-ending work. This is used when we finish reading a line
* in read_line_timeout(), or when we cancel an interrupted line, thus
* finishing the line, in read_line_cancel().
*/
void CVmConsole::read_line_done(VMG0_)
{
/* if we have a line in progress, finish it off */
if (S_read_in_progress)
{
/* set the original 'more' mode */
set_more_state(S_old_more_mode);
/*
* Write the input line, followed by a newline, to the log file.
* Note that the text is still in the local character set, so we
* can write it directly to the log file.
*
* If we're reading from a script file in "echo" mode, skip this.
* When reading from a script file in "echo" mode, we will manually
* copy the input commands to the main console, which will
* automatically copy to the main log file. If we're in quiet
* scripting mode, though, we won't do that, so we do need to
* capture the input explicitly here.
*/
if (log_enabled_ && (script_sp_ == 0 || script_sp_->quiet))
{
log_str_->print_to_os(S_read_buf);
log_str_->print_to_os("\n");
}
/* note in the streams that we've read an input line */
disp_str_->note_input_line();
if (log_str_ != 0)
log_str_->note_input_line();
/* clear the in-progress flag */
S_read_in_progress = FALSE;
}
}
/* ------------------------------------------------------------------------ */
/*
* Read an input event from the script file. If we're reading an event
* script file, we'll read the next event and return TRUE; if we're not
* reading a script file, or the script file is a command-line script
* rather than an event script, we'll simply return FALSE.
*
* If the event takes a parameter, we'll read the parameter into 'buf'.
* The value is returned in the local character set, so the caller will
* need to translate it to UTF-8.
*
* If 'filter' is non-null, we'll only return events of the types in the
* filter list.
*/
int CVmConsole::read_event_script(VMG_ int *evt, char *buf, size_t buflen,
const int *filter, int filter_cnt,
unsigned long *attrs)
{
/*
* if we're not reading a script, or it's not an event script, skip
* this
*/
if (script_sp_ == 0 || !script_sp_->event_script)
return FALSE;
/* get the script file */
osfildef *fp = script_sp_->fp;
/* keep going until we find something */
for (;;)
{
/* read the next event */
if (!read_script_event_type(evt, attrs))
{
/* end of the script - close it */
set_more_state(close_script_file());
/* if there's no more script file, there's no event */
if (script_sp_ == 0)
return FALSE;
/* go back for the next event */
fp = script_sp_->fp;
continue;
}
/* if it's not in the filter list, skip it */
if (filter != 0)
{
int i, found;
/* look for a match in our filter list */
for (i = 0, found = FALSE ; i < filter_cnt ; ++i)
{
if (filter[i] == *evt)
{
found = TRUE;
break;
}
}
/* if we didn't find it, skip this line */
if (!found)
{
skip_script_line(fp);
continue;
}
}
/* if there's a buffer, read the rest of the line */
if (buf != 0)
{
/* read the parameter into the buffer, and return the result */
if (!read_script_param(buf, buflen, fp))
return FALSE;
/* if this is an OS_EVT_KEY event, translate special keys */
if (*evt == OS_EVT_KEY)
{
if (strcmp(buf, "[enter]") == 0)
strcpy(buf, "\n");
else if (strcmp(buf, "[tab]") == 0)
strcpy(buf, "\t");
else if (strcmp(buf, "[space]") == 0)
strcpy(buf, " ");
}
}
else
{
/* no result buffer - just skip anything left on the line */
skip_script_line(fp);
}
/* success */
return TRUE;
}
}
/*
* Read a <tag> or attribute token from a script file. Returns the
* character after the end of the token.
*/
static int read_script_token(char *buf, size_t buflen, osfildef *fp)
{
char *p;
int c;
/* skip leading whitespace */
for (c = osfgetc(fp) ; isspace(c) ; c = osfgetc(fp)) ;
/* read from the file until we reach the end of the token */
for (p = buf ;
p < buf + buflen - 1
&& c != '>' && !isspace(c)
&& c != '\n' && c != '\r' && c != EOF ; )
{
/* store this character */
*p++ = (char)c;
/* get the next one */
c = osfgetc(fp);
}
/* null-terminate the token */
*p = '\0';
/* return the character that ended the token */
return c;
}
/*
* Read the next event type from current event script file. This leaves
* the file positioned at the parameter data for the event, if any.
* Returns FALSE if we reach end of file without finding an event.
*/
int CVmConsole::read_script_event_type(int *evt, unsigned long *attrs)
{
/* clear the caller's attribute flags, if provided */
if (attrs != 0)
*attrs = 0;
/* if there's no script, there's no event */
if (script_sp_ == 0)
return FALSE;
/* get the file */
osfildef *fp = script_sp_->fp;
/* if it's a command-line script, there are only line input events */
if (!script_sp_->event_script)
{
/* keep going until we find an input line */
for (;;)
{
/* read the first charater of the line */
int c = osfgetc(fp);
if (c == '>')
{
/* we found a line input event */
*evt = OS_EVT_LINE;
return TRUE;
}
else if (c == EOF)
{
/* end of file - give up */
return FALSE;
}
else
{
/*
* anything else is just a comment line - just skip it and
* keep looking
*/
skip_script_line(fp);
}
}
}
/* keep going until we find an event tag */
for (;;)
{
int c;
char tag[32];
static const struct
{
const char *tag;
int evt;
}
*tp, tags[] =
{
{ "key", OS_EVT_KEY },
{ "timeout", OS_EVT_TIMEOUT },
{ "notimeout", OS_EVT_NOTIMEOUT },
{ "eof", OS_EVT_EOF },
{ "line", OS_EVT_LINE },
{ "command", OS_EVT_COMMAND },
{ "endqs", VMCON_EVT_END_SCRIPT },
{ "dialog", VMCON_EVT_DIALOG },
{ "file", VMCON_EVT_FILE },
{ 0, 0 }
};
/* check the start of the line to make sure it's an event */
c = osfgetc(fp);
/* if at EOF, return failure */
if (c == EOF)
return FALSE;
/* if it's not an event code line, skip the line */
if (c != '<')
{
skip_script_line(fp);
continue;
}
/* read the event type (up to the '>') */
c = read_script_token(tag, sizeof(tag), fp);
/* check for attributes */
while (isspace(c))
{
char attr[32];
static const struct
{
const char *name;
unsigned long flag;
}
*ap, attrlist[] =
{
{ "overwrite", VMCON_EVTATTR_OVERWRITE },
{ 0, 0 }
};
/* read the attribute name */
c = read_script_token(attr, sizeof(attr), fp);
/* if the name is empty, stop */
if (attr[0] == '\0')
break;
/* look up the token */
for (ap = attrlist ; ap->name != 0 ; ++ap)
{
/* check for a match */
if (stricmp(attr, ap->name) == 0)
{
/* if the caller wants the flag, set it */
if (attrs != 0)
*attrs |= ap->flag;
/* no need to look any further */
break;
}
}
/* if we're at the '>' or at end of line or file, stop */
if (c == '>' || c == '\n' || c == '\r' || c == EOF)
break;
}
/* if it's not a well-formed tag, ignore it */
if (c != '>')
{
skip_script_line(fp);
continue;
}
/* look up the tag */
for (tp = tags ; tp->tag != 0 ; ++tp)
{
/* check for a match to this tag name */
if (stricmp(tp->tag, tag) == 0)
{
/* got it - return the event type */
*evt = tp->evt;
return TRUE;
}
}
/* we don't recognize the tag name; skip the line and keep looking */
skip_script_line(fp);
}
}
/*
* Skip to the next script line
*/
void CVmConsole::skip_script_line(osfildef *fp)
{
int c;
/* read until we find the end of the current line */
for (c = osfgetc(fp) ; c != EOF && c != '\n' && c != '\r' ;
c = osfgetc(fp)) ;
}
/*
* read the rest of the current script line into the given buffer
*/
int CVmConsole::read_script_param(char *buf, size_t buflen, osfildef *fp)
{
/* ignore zero-size buffer requests */
if (buflen == 0)
return FALSE;
/* read characters until we run out of buffer or reach a newline */
for (;;)
{
/* get the next character */
int c = osfgetc(fp);
/* if it's a newline or end of file, we're done */
if (c == '\n' || c == EOF)
{
/* null-terminate the buffer */
*buf = '\0';
/* indicate success */
return TRUE;
}
/*
* if there's room in the buffer, add the character - always leave
* one byte for the null terminator
*/
if (buflen > 1)
{
*buf++ = (char)c;
--buflen;
}
}
}
/*
* Read a line of text from the script file, if there is one. Returns TRUE
* on success, FALSE if we reach the end of the script file or encounter
* any other error.
*/
int CVmConsole::read_line_from_script(char *buf, size_t buflen, int *evt)
{
/* if there's no script file, return failure */
if (script_sp_ == 0)
return FALSE;
/* get the file from the script stack */
osfildef *fp = script_sp_->fp;
/* keep going until we find a line that we like */
for (;;)
{
/* read the script according to its type ('event' or 'line input') */
if (script_sp_->event_script)
{
/* read to the next event */
if (!read_script_event_type(evt, 0))
return FALSE;
/* check the event code */
switch (*evt)
{
case OS_EVT_LINE:
case OS_EVT_TIMEOUT:
/*
* it's one of our line input events - read the line (or
* partial line, in the case of TIMEOUT)
*/
return read_script_param(buf, buflen, fp);
default:
/*
* it's not our type of event - skip the rest of the line
* and keep looking
*/
skip_script_line(fp);
break;
}
}
else
{
/*
* We have a basic line-input script rather than an event
* script. Each input line starts with a '>'; everything else
* is a comment.
*
* Read the first character on the line. If it's not a
* newline, there's more text on the same line, so read the
* rest and determine what to do.
*/
int c = osfgetc(fp);
if (c == '>')
{
/* it's a command line - read it */
*evt = OS_EVT_LINE;
return read_script_param(buf, buflen, fp);
}
else if (c == EOF)
{
/* end of file */
return FALSE;
}
else if (c == '\n' || c == '\r')
{
/* blank line - continue on to the next line */
}
else
{
/* it's not a command line - just skip it and keep looking */
skip_script_line(fp);
}
}
}
}
/* ------------------------------------------------------------------------ */
/*
* Main System Console
*/
/*
* create
*/
CVmConsoleMain::CVmConsoleMain(VMG0_)
{
/* create the system banner manager */
banner_manager_ = new CVmBannerManager();
/* create the log console manager */
log_console_manager_ = new CVmLogConsoleManager();
/* create and initialize our display stream */
main_disp_str_ = new CVmFormatterMain(this, 256);
main_disp_str_->init();
/* initially send text to the main display stream */
disp_str_ = main_disp_str_;
/*
* Create our log stream. The main console always has a log stream,
* even when it's not in use, so that we can keep the log stream's
* state synchronized with the display stream in preparation for
* activation.
*/
log_str_ = new CVmFormatterLog(this, 80);
/*
* use the default log file character mapper - on some systems, files
* don't use the same character set as the display
*/
log_str_->set_charmap(G_cmap_to_log);
/* initialize the log stream */
log_str_->init();
/*
* the log stream is initially enabled (this is separate from the log
* file being opened; it merely indicates that we send output
* operations to the log stream for processing)
*/
log_enabled_ = TRUE;
/* we don't have a statusline formatter until asked for one */
statline_str_ = 0;
/* reset statics */
S_read_in_progress = FALSE;
S_read_buf[0] = '\0';
}
/*
* delete
*/
CVmConsoleMain::~CVmConsoleMain()
{
/* delete the system banner manager */
banner_manager_->delete_obj();
/* delete the system log console manager */
log_console_manager_->delete_obj();
/* delete the display stream */
delete main_disp_str_;
/* delete the statusline stream, if we have one */
if (statline_str_ != 0)
delete statline_str_;
}
/*
* Clear the window
*/
void CVmConsoleMain::clear_window(VMG0_)
{
/* flush and empty our output buffer */
flush(vmg_ VM_NL_NONE);
empty_buffers(vmg0_);
/* clear the main window */
oscls();
/* reset the MORE line counter in the display stream */
disp_str_->reset_line_count(TRUE);
}
/*
* Set statusline mode
*/
void CVmConsoleMain::set_statusline_mode(VMG_ int mode)
{
CVmFormatterDisp *str;
/*
* if we're switching into statusline mode, and we don't have a
* statusline stream yet, create one
*/
if (mode && statline_str_ == 0)
{
/* create and initialize the statusline stream */
statline_str_ = new CVmFormatterStatline(this);
statline_str_->init();
}
/* get the stream selected by the new mode */
if (mode)
str = statline_str_;
else
str = main_disp_str_;
/* if this is already the active stream, we have nothing more to do */
if (str == disp_str_)
return;
/* make the new stream current */
disp_str_ = str;
/*
* check which mode we're switching to, so we can do some extra work
* specific to each mode
*/
if (mode)
{
/*
* we're switching to the status line, so disable the log stream -
* statusline text is never sent to the log, since the log reflects
* only what was displayed in the main text area
*/
log_enabled_ = FALSE;
}
else
{
/*
* we're switching back to the main stream, so flush the statusline
* so we're sure the statusline text is displayed
*/
/* end the line */
statline_str_->format_text(vmg_ "\n", 1);
/* flush output */
statline_str_->flush(vmg_ VM_NL_NONE);
/* re-enable the log stream, if we have one */
if (log_str_ != 0)
log_enabled_ = TRUE;
}
/* switch at the OS layer */
os_status(mode);
}
/*
* Flush everything
*/
void CVmConsoleMain::flush_all(VMG_ vm_nl_type nl)
{
/* flush our primary console */
flush(vmg_ nl);
/*
* Flush each banner we're controlling. Note that we explicitly flush
* the banners with newline mode 'NONE', regardless of the newline mode
* passed in by the caller: the caller's mode is for the primary
* console, but for the banners we just want to make sure they're
* flushed out normally, since whatever we're doing in the primary
* console that requires flushing doesn't concern the banners.
*/
banner_manager_->flush_all(vmg_ VM_NL_NONE);
}
/* ------------------------------------------------------------------------ */
/*
* Handle manager
*/
/* initialize */
CVmHandleManager::CVmHandleManager()
{
size_t i;
/* allocate an initial array of handle slots */
handles_max_ = 32;
handles_ = (void **)t3malloc(handles_max_ * sizeof(*handles_));
/* all slots are initially empty */
for (i = 0 ; i < handles_max_ ; ++i)
handles_[i] = 0;
}
/* delete the object - this is the public destructor interface */
void CVmHandleManager::delete_obj()
{
size_t i;
/*
* Delete each remaining object. Note that we need to call the virtual
* delete_handle_object routine, so we must do this before reaching the
* destructor (once in the base class destructor, we no longer have
* access to the subclass virtuals).
*/
for (i = 0 ; i < handles_max_ ; ++i)
{
/* if this banner is still valid, delete it */
if (handles_[i] != 0)
delete_handle_object(i + 1, handles_[i]);
}
/* delete the object */
delete this;
}
/* destructor */
CVmHandleManager::~CVmHandleManager()
{
/* delete the handle pointer array */
t3free(handles_);
}
/*
* Allocate a new handle
*/
int CVmHandleManager::alloc_handle(void *item)
{
size_t slot;
/* scan for a free slot */
for (slot = 0 ; slot < handles_max_ ; ++slot)
{
/* if this one is free, use it */
if (handles_[slot] == 0)
break;
}
/* if we didn't find a free slot, extend the array */
if (slot == handles_max_)
{
size_t i;
/* allocate a larger array */
handles_max_ += 32;
handles_ = (void **)
t3realloc(handles_, handles_max_ * sizeof(*handles_));
/* clear out the newly-allocated slots */
for (i = slot ; i < handles_max_ ; ++i)
handles_[i] = 0;
}
/* store the new item in our pointer array */
handles_[slot] = item;
/*
* convert the slot number to a handle by adjusting it to a 1-based
* index, and return the result
*/
return slot + 1;
}
/* ------------------------------------------------------------------------ */
/*
* Banner manager
*/
/*
* Create a banner
*/
int CVmBannerManager::create_banner(VMG_ int parent_id,
int where, int other_id,
int wintype, int align,
int siz, int siz_units,
unsigned long style)
{
void *handle;
void *parent_handle;
void *other_handle;
CVmConsoleBanner *item;
/* get the parent handle, if provided */
parent_handle = get_os_handle(parent_id);
/* get the 'other' handle, if we need it for the 'where' */
switch(where)
{
case OS_BANNER_BEFORE:
case OS_BANNER_AFTER:
/* retrieve the handle for the other_id */
other_handle = get_os_handle(other_id);
break;
default:
/* we don't need 'other' for other 'where' modes */
other_handle = 0;
break;
}
/* try creating the OS-level banner window */
handle = os_banner_create(parent_handle, where, other_handle, wintype,
align, siz, siz_units, style);
/* if we couldn't create the OS-level window, return failure */
if (handle == 0)
return 0;
/* create the new console */
item = new CVmConsoleBanner(handle, wintype, style);
/* allocate a handle for the new banner, and return the handle */
return alloc_handle(item);
}
/*
* Delete or orphan a banner window. Deleting and orphaning both sever
* all ties from the banner manager (and thus from the T3 program) to the
* banner. Deleting a banner actually gets deletes it at the OS level;
* orphaning the banner severs our ties, but hands the banner over to the
* OS to do with as it pleases. On some implementations, the OS will
* continue to display the banner after it's orphaned to allow the final
* display configuration to remain visible even after the program has
* terminated.
*/
void CVmBannerManager::delete_or_orphan_banner(int banner, int orphan)
{
CVmConsoleBanner *item;
void *handle;
/* if the banner is invalid, ignore the request */
if ((item = (CVmConsoleBanner *)get_object(banner)) == 0)
return;
/* get the OS-level banner handle */
handle = item->get_os_handle();
/* delete the banner item */
delete item;
/* clear the slot */
clear_handle(banner);
/* delete the OS-level banner */
if (orphan)
os_banner_orphan(handle);
else
os_banner_delete(handle);
}
/*
* Get the OS-level handle for the given banner
*/
void *CVmBannerManager::get_os_handle(int banner)
{
CVmConsoleBanner *item;
/* if the banner is invalid, return failure */
if ((item = (CVmConsoleBanner *)get_object(banner)) == 0)
return 0;
/* return the handle from the slot */
return item->get_os_handle();
}
/*
* Flush all banners
*/
void CVmBannerManager::flush_all(VMG_ vm_nl_type nl)
{
size_t slot;
/* flush each banner */
for (slot = 0 ; slot < handles_max_ ; ++slot)
{
/* if this slot has a valid banner, flush it */
if (handles_[slot] != 0)
((CVmConsoleBanner *)handles_[slot])->flush(vmg_ nl);
}
}
/* ------------------------------------------------------------------------ */
/*
* Banner Window Console
*/
CVmConsoleBanner::CVmConsoleBanner(void *banner_handle, int win_type,
unsigned long style)
{
CVmFormatterBanner *str;
os_banner_info_t info;
int obey_whitespace = FALSE;
int literal_mode = FALSE;
/* remember our OS-level banner handle */
banner_ = banner_handle;
/* get osifc-level information on the banner */
if (!os_banner_getinfo(banner_, &info))
info.os_line_wrap = FALSE;
/*
* If it's a text grid window, don't do any line wrapping. Text grids
* simply don't have any line wrapping, so we don't want to impose any
* at the formatter level. Set the formatter to "os line wrap" mode,
* to indicate that the formatter doesn't do wrapping - even though
* the underlying OS banner window won't do any wrapping either, the
* lack of line wrapping counts as OS handling of line wrapping.
*/
if (win_type == OS_BANNER_TYPE_TEXTGRID)
{
/* do not wrap lines in the formatter */
info.os_line_wrap = TRUE;
/* use literal mode, and obey whitespace literally */
literal_mode = TRUE;
obey_whitespace = TRUE;
}
/* create and initialize our display stream */
disp_str_ = str = new CVmFormatterBanner(banner_handle, this,
win_type, style);
str->init_banner(info.os_line_wrap, obey_whitespace, literal_mode);
/* remember our window type */
win_type_ = win_type;
}
/*
* Deletion
*/
CVmConsoleBanner::~CVmConsoleBanner()
{
/* delete our display stream */
delete disp_str_;
}
/*
* Clear the banner window
*/
void CVmConsoleBanner::clear_window(VMG0_)
{
/* flush and empty our output buffer */
flush(vmg_ VM_NL_NONE);
empty_buffers(vmg0_);
/* clear our underlying system banner */
os_banner_clear(banner_);
/* tell our display stream to zero its line counter */
disp_str_->reset_line_count(TRUE);
}
/*
* Get banner information
*/
int CVmConsoleBanner::get_banner_info(os_banner_info_t *info)
{
int ret;
/* get the OS-level information */
ret = os_banner_getinfo(banner_, info);
/* make some adjustments if we got valid information back */
if (ret)
{
/*
* check the window type for further adjustments we might need to
* make to the data returned from the OS layer
*/
switch(win_type_)
{
case OS_BANNER_TYPE_TEXTGRID:
/*
* text grids don't support <TAB> alignment, even if the
* underlying OS banner says we do, because we simply don't
* support <TAB> (or any other HTML markups) in a text grid
* window
*/
info->style &= ~OS_BANNER_STYLE_TAB_ALIGN;
break;
default:
/* other types don't require any adjustments */
break;
}
}
/* return the success indication */
return ret;
}
/* ------------------------------------------------------------------------ */
/*
* Log file console manager
*/
/*
* create a log console
*/
int CVmLogConsoleManager::create_log_console(const char *fname,
osfildef *fp,
class CCharmapToLocal *cmap,
int width)
{
CVmConsoleLog *con;
/* create the new console */
con = new CVmConsoleLog(fname, fp, cmap, width);
/* allocate a handle for the new console and return the handle */
return alloc_handle(con);
}
/*
* delete log a console
*/
void CVmLogConsoleManager::delete_log_console(int handle)
{
CVmConsoleLog *con;
/* if the handle is invalid, ignore the request */
if ((con = (CVmConsoleLog *)get_object(handle)) == 0)
return;
/* delete the console */
delete con;
/* clear the slot */
clear_handle(handle);
}
/* ------------------------------------------------------------------------ */
/*
* Log file console
*/
CVmConsoleLog::CVmConsoleLog(const char *fname, osfildef *fp,
class CCharmapToLocal *cmap, int width)
{
CVmFormatterLog *str;
/* create our display stream */
disp_str_ = str = new CVmFormatterLog(this, width);
/* set the file */
str->set_log_file(fname, fp);
/* set the character mapper */
str->set_charmap(cmap);
}
/*
* destroy
*/
CVmConsoleLog::~CVmConsoleLog()
{
/* delete our display stream */
delete disp_str_;
}
|