Comments and refactorings (hidden -> masked) in the TextInput.
This commit is contained in:
parent
515944b3ac
commit
60660df4b7
@ -72,7 +72,7 @@ namespace textinput {
|
||||
Range
|
||||
Editor::ResetText() {
|
||||
bool addToHist = !fContext->GetLine().empty()
|
||||
&& !fContext->GetTextInput()->IsInputHidden()
|
||||
&& !fContext->GetTextInput()->IsInputMasked()
|
||||
&& fContext->GetTextInput()->IsAutoHistAddEnabled();
|
||||
if (addToHist) {
|
||||
fContext->GetHistory()->AddLine(fContext->GetLine().GetText());
|
||||
|
@ -7,7 +7,7 @@
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This file defines the abtract input interface.
|
||||
// This file defines the abstract input interface.
|
||||
//
|
||||
// Axel Naumann <axel@cern.ch>, 2011-05-12
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
@ -131,11 +131,19 @@ namespace textinput {
|
||||
StreamReaderUnix::StreamReaderUnix():
|
||||
fHaveInputFocus(false), fIsTTY(isatty(fileno(stdin))) {
|
||||
#ifdef TCSANOW
|
||||
// ~ISTRIP - do not strip 8th char bit
|
||||
// ~IXOFF - software flow ctrl disabled for input queue
|
||||
TerminalConfigUnix::Get().TIOS()->c_iflag &= ~(ISTRIP|IXOFF);
|
||||
// BRKINT - flush i/o and send SIGINT
|
||||
// INLCR - translate NL to CR
|
||||
TerminalConfigUnix::Get().TIOS()->c_iflag |= BRKINT | INLCR;
|
||||
// ~ICANON - non-canonical = input available immediately, no EOL needed, no processing, line editing disabled
|
||||
// ~ISIG - don't sent signals on input chars
|
||||
// ~TOSTOP - don't send SIGTTOU
|
||||
// ~IEXTEN - disable implementation-defined input processing, don't process spec chars (EOL2, LNEXT...)
|
||||
TerminalConfigUnix::Get().TIOS()->c_lflag &= ~(ICANON|ISIG|TOSTOP|IEXTEN);
|
||||
TerminalConfigUnix::Get().TIOS()->c_cc[VMIN] = 1;
|
||||
TerminalConfigUnix::Get().TIOS()->c_cc[VTIME] = 0;
|
||||
TerminalConfigUnix::Get().TIOS()->c_cc[VMIN] = 1; // minimum chars to read in non-canonical mode
|
||||
TerminalConfigUnix::Get().TIOS()->c_cc[VTIME] = 0; // waits indefinitely for VMIN chars (blocking)
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -143,6 +151,8 @@ namespace textinput {
|
||||
ReleaseInputFocus();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// Attach to terminal, set the proper configuration.
|
||||
void
|
||||
StreamReaderUnix::GrabInputFocus() {
|
||||
// set to raw i.e. unbuffered
|
||||
@ -151,6 +161,8 @@ namespace textinput {
|
||||
fHaveInputFocus = true;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// Detach from terminal, set the old configuration.
|
||||
void
|
||||
StreamReaderUnix::ReleaseInputFocus() {
|
||||
// set to buffered
|
||||
@ -159,6 +171,12 @@ namespace textinput {
|
||||
fHaveInputFocus = false;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// Test or wait for available input
|
||||
///
|
||||
/// \param[in] wait blocking wait on input
|
||||
///
|
||||
/// Wait true - block, wait false - poll
|
||||
bool
|
||||
StreamReaderUnix::HavePendingInput(bool wait) {
|
||||
if (!fReadAheadBuffer.empty())
|
||||
@ -172,6 +190,10 @@ namespace textinput {
|
||||
return (avail == 1);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// Process Control Sequence Introducer commands (equivalent to ESC [)
|
||||
///
|
||||
/// \param[in] in input char / data
|
||||
bool
|
||||
StreamReaderUnix::ProcessCSI(InputData& in) {
|
||||
static ExtKeyMap gExtKeyMap;
|
||||
@ -231,25 +253,30 @@ namespace textinput {
|
||||
return ret != InputData::kEIUninitialized;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// Read one char from stdin. Converts the read char to InputData
|
||||
///
|
||||
/// \param[in] nRead number of already read characters. Increment after reading
|
||||
/// \param[in] in input char / data to be filled out
|
||||
bool
|
||||
StreamReaderUnix::ReadInput(size_t& nRead, InputData& in) {
|
||||
int c = ReadRawCharacter();
|
||||
in.SetModifier(InputData::kModNone);
|
||||
if (c == -1) {
|
||||
if (c == -1) { // non-character value, EOF negative
|
||||
in.SetExtended(InputData::kEIEOF);
|
||||
} else if (c == 0x1b) {
|
||||
// Only try to process CSI if Esc does not have a meaning
|
||||
} else if (c == 0x1b) { // ESC
|
||||
// Only try to process CSI if Esc does not have a meaning by itself.
|
||||
// by itself.
|
||||
if (GetContext()->GetKeyBinding()->IsEscCommandEnabled()
|
||||
|| !ProcessCSI(in)) {
|
||||
in.SetExtended(InputData::kEIEsc);
|
||||
}
|
||||
} else if (isprint(c)) {
|
||||
} else if (isprint(c)) { // c >= 0x20(32) && c < 0x7f(127)
|
||||
in.SetRaw(c);
|
||||
} else if (c < 32 || c == (char)127 /* ^?, DEL on MacOS */) {
|
||||
if (c == 13) {
|
||||
} else if (c < 32 || c == (char)127 /* ^?, DEL on MacOS */) { // non-printable
|
||||
if (c == 13) { // 0x0d CR (INLCR - NL converted to CR)
|
||||
in.SetExtended(InputData::kEIEnter);
|
||||
} else {
|
||||
} else { // mark CTRL pressed if other non-print char
|
||||
in.SetRaw(c);
|
||||
in.SetModifier(InputData::kModCtrl);
|
||||
}
|
||||
@ -260,6 +287,9 @@ namespace textinput {
|
||||
++nRead;
|
||||
return true;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// Read one character from stdin. Block if not available.
|
||||
int
|
||||
StreamReaderUnix::ReadRawCharacter() {
|
||||
char buf;
|
||||
|
@ -37,21 +37,31 @@ namespace textinput {
|
||||
#endif
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// Notify the display that the text has been changed in range r.
|
||||
/// Rewrite the display in range r and move back to the cursor.
|
||||
///
|
||||
/// \param[in] r Range to write out the text for.
|
||||
void
|
||||
TerminalDisplay::NotifyTextChange(Range r) {
|
||||
if (!IsTTY()) return;
|
||||
Attach();
|
||||
WriteWrapped(r.fPromptUpdate,GetContext()->GetTextInput()->IsInputHidden(),
|
||||
WriteWrapped(r.fPromptUpdate, GetContext()->GetTextInput()->IsInputMasked(),
|
||||
r.fStart, r.fLength);
|
||||
Move(GetCursor());
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// Notify the display that the cursor has been changed. Move to the cursor.
|
||||
void
|
||||
TerminalDisplay::NotifyCursorChange() {
|
||||
Attach();
|
||||
Move(GetCursor());
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// Notify the display that the input has been taken.
|
||||
/// Move to the next line, reset written length and position.
|
||||
void
|
||||
TerminalDisplay::NotifyResetInput() {
|
||||
Attach();
|
||||
@ -62,12 +72,20 @@ namespace textinput {
|
||||
fWritePos = Pos();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// Notify the display that there has been an error.
|
||||
/// Write out the BEL character.
|
||||
void
|
||||
TerminalDisplay::NotifyError() {
|
||||
Attach();
|
||||
WriteRawString("\x07", 1);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// Display an informational message at the prompt.
|
||||
/// Acts like a pop-up. Used e.g. for tab-completion.
|
||||
///
|
||||
/// \param[in] Options options to write out
|
||||
void
|
||||
TerminalDisplay::DisplayInfo(const std::vector<std::string>& Options) {
|
||||
char infoColIdx = 0;
|
||||
@ -77,7 +95,7 @@ namespace textinput {
|
||||
WriteRawString("\n", 1);
|
||||
for (size_t i = 0, n = Options.size(); i < n; ++i) {
|
||||
Text t(Options[i], infoColIdx);
|
||||
WriteWrappedElement(t, 0, 0, (size_t) -1);
|
||||
WriteWrappedTextPart(t, 0, 0, (size_t) -1);
|
||||
WriteRawString("\n", 1);
|
||||
}
|
||||
// Reset position
|
||||
@ -85,6 +103,9 @@ namespace textinput {
|
||||
Attach();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// Detach from the abstract display by resetting the position
|
||||
/// and written text length. If Colorizer is present, reset the color too.
|
||||
void
|
||||
TerminalDisplay::Detach() {
|
||||
fWritePos = Pos();
|
||||
@ -98,41 +119,54 @@ namespace textinput {
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// Write out wrapped text to the display. Used in WriteWrapped and DisplayInfo
|
||||
///
|
||||
/// \param[in] text text to write out
|
||||
/// \param[in] TextOffset where to begin writing out text from
|
||||
/// \param[in] WriteOffset where to begin writing out text at the display
|
||||
/// \param[in] Requested number of text characters requested for output
|
||||
size_t
|
||||
TerminalDisplay::WriteWrappedElement(const Text& Element, size_t TextOffset,
|
||||
size_t WriteOffset, size_t Requested) {
|
||||
TerminalDisplay::WriteWrappedTextPart(const Text &text, size_t TextOffset,
|
||||
size_t WriteOffset, size_t NumRequested) {
|
||||
size_t Start = TextOffset;
|
||||
size_t Remaining = Requested;
|
||||
size_t NumRemaining = NumRequested; // optimistic
|
||||
|
||||
size_t Available = Element.length() - Start;
|
||||
if (Requested == (size_t) -1) {
|
||||
Requested = Available;
|
||||
size_t NumAvailable = text.length() - Start;
|
||||
if (NumRequested == (size_t) -1) { // requested max available
|
||||
NumRequested = NumAvailable;
|
||||
}
|
||||
|
||||
if (Available > 0) {
|
||||
if (Available < Remaining) {
|
||||
Remaining = Available;
|
||||
// If we have some text available for output
|
||||
if (NumAvailable > 0) {
|
||||
// If we don't have enough to output NumRemaining, output only what's available
|
||||
if (NumAvailable < NumRemaining) {
|
||||
NumRemaining = NumAvailable;
|
||||
}
|
||||
|
||||
while (Remaining > 0) {
|
||||
size_t numThisLine = Remaining;
|
||||
|
||||
while (NumRemaining > 0) {
|
||||
// How much can this line hold?
|
||||
size_t numToEOL = GetWidth() - ((Start + WriteOffset) % GetWidth());
|
||||
if (!numToEOL) {
|
||||
if (numToEOL == 0) { // we are at EOL, move down
|
||||
MoveDown();
|
||||
++fWritePos.fLine;
|
||||
MoveFront();
|
||||
fWritePos.fCol = 0;
|
||||
numToEOL = GetWidth();
|
||||
}
|
||||
if (numThisLine > numToEOL) {
|
||||
|
||||
// How much of our text can we fit in this line?
|
||||
size_t numThisLine;
|
||||
if (NumRemaining > numToEOL) {
|
||||
numThisLine = numToEOL;
|
||||
} else {
|
||||
numThisLine = NumRemaining;
|
||||
}
|
||||
|
||||
// If there is a Colorizer, we only write same-colored chunks.
|
||||
// How long is the current chunk? Adjust numThisLine.
|
||||
if (GetContext()->GetColorizer()) {
|
||||
// We only write same-color chunks; how long is it?
|
||||
const std::vector<char>& Colors = Element.GetColors();
|
||||
const std::vector<char>& Colors = text.GetColors();
|
||||
char ThisColor = Colors[Start];
|
||||
size_t numSameColor = 1;
|
||||
while (numSameColor < numThisLine
|
||||
@ -148,31 +182,36 @@ namespace textinput {
|
||||
}
|
||||
}
|
||||
|
||||
WriteRawString(Element.GetText().c_str() + Start, numThisLine);
|
||||
// Write out the line and update the write position
|
||||
WriteRawString(text.GetText().c_str() + Start, numThisLine);
|
||||
fWritePos = IndexToPos(PosToIndex(fWritePos) + numThisLine);
|
||||
if (numThisLine == numToEOL) {
|
||||
if (numThisLine == numToEOL) { // If we hit EOL, wrap around
|
||||
ActOnEOL();
|
||||
}
|
||||
|
||||
Start += numThisLine;
|
||||
Remaining -= numThisLine;
|
||||
NumRemaining -= numThisLine;
|
||||
}
|
||||
}
|
||||
|
||||
if (Requested == Available) {
|
||||
size_t VisL = fWriteLen / GetWidth();
|
||||
size_t Wrote = WriteOffset + TextOffset + Requested;
|
||||
size_t WroteL = Wrote / GetWidth();
|
||||
size_t NumToEOL = GetWidth() - (Wrote % GetWidth());
|
||||
if (fWriteLen > Wrote && NumToEOL > 0) {
|
||||
// Wrote less and not at EOL
|
||||
// If we have processed the characters we have requested
|
||||
if (NumRequested == NumAvailable) {
|
||||
size_t NumPrevLines = fWriteLen / GetWidth();
|
||||
size_t LenWrote = WriteOffset + TextOffset + NumRequested;
|
||||
size_t NumWroteLines = LenWrote / GetWidth();
|
||||
size_t NumToEOL = GetWidth() - (LenWrote % GetWidth());
|
||||
if (LenWrote < fWriteLen && NumToEOL > 0) {
|
||||
// If we wrote less than previously and not at EOL
|
||||
// Erase the rest of the current line
|
||||
EraseToRight();
|
||||
}
|
||||
if (WroteL < VisL) {
|
||||
if (NumWroteLines < NumPrevLines) {
|
||||
// If we wrote less lines than previously,
|
||||
// erase the surplus previous lines
|
||||
Pos prevWC = GetCursor();
|
||||
MoveFront();
|
||||
fWritePos.fCol = 0;
|
||||
for (size_t l = WroteL + 1; l <= VisL; ++l) {
|
||||
for (size_t l = NumWroteLines + 1; l <= NumPrevLines; ++l) {
|
||||
MoveDown();
|
||||
++fWritePos.fLine;
|
||||
EraseToRight();
|
||||
@ -180,11 +219,11 @@ namespace textinput {
|
||||
Move(prevWC);
|
||||
}
|
||||
}
|
||||
return Remaining;
|
||||
return NumRemaining;
|
||||
}
|
||||
|
||||
size_t
|
||||
TerminalDisplay::WriteWrapped(Range::EPromptUpdate PromptUpdate, bool hidden,
|
||||
TerminalDisplay::WriteWrapped(Range::EPromptUpdate PromptUpdate, bool masked,
|
||||
size_t Offset, size_t Requested /* = -1*/) {
|
||||
Attach();
|
||||
|
||||
@ -199,16 +238,18 @@ namespace textinput {
|
||||
PromptUpdate = Range::kNoPromptUpdate;
|
||||
}
|
||||
|
||||
// If updating prompt, write the main prompt first (e.g. [cling]$)
|
||||
if (PromptUpdate & Range::kUpdatePrompt) {
|
||||
// Writing from front means we write the prompt, too
|
||||
Move(Pos());
|
||||
WriteWrappedElement(Prompt, 0, 0, PromptLen);
|
||||
WriteWrappedTextPart(Prompt, 0, 0, PromptLen);
|
||||
}
|
||||
// If updating any prompt
|
||||
if (PromptUpdate != Range::kNoPromptUpdate) {
|
||||
// Any prompt update means we'll have to re-write the editor prompt
|
||||
Move(IndexToPos(PromptLen));
|
||||
if (EditorPromptLen) {
|
||||
WriteWrappedElement(EditPrompt, 0, PromptLen, EditorPromptLen);
|
||||
WriteWrappedTextPart(EditPrompt, 0, PromptLen, EditorPromptLen);
|
||||
}
|
||||
// Any prompt update means we'll have to re-write the text
|
||||
Offset = 0;
|
||||
@ -217,18 +258,22 @@ namespace textinput {
|
||||
Move(IndexToPos(PromptLen + EditorPromptLen + Offset));
|
||||
|
||||
size_t avail = 0;
|
||||
if (hidden) {
|
||||
Text hide(std::string(GetContext()->GetLine().length(), '*'), 0);
|
||||
avail = WriteWrappedElement(hide, Offset,
|
||||
PromptLen + EditorPromptLen, Requested);
|
||||
if (masked) {
|
||||
Text mask(std::string(GetContext()->GetLine().length(), '*'), 0);
|
||||
avail = WriteWrappedTextPart(mask, Offset,
|
||||
PromptLen + EditorPromptLen, Requested);
|
||||
} else {
|
||||
avail = WriteWrappedElement(GetContext()->GetLine(), Offset,
|
||||
PromptLen + EditorPromptLen, Requested);
|
||||
avail = WriteWrappedTextPart(GetContext()->GetLine(), Offset,
|
||||
PromptLen + EditorPromptLen, Requested);
|
||||
}
|
||||
fWriteLen = PromptLen + EditorPromptLen + GetContext()->GetLine().length();
|
||||
return avail;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// Move the cursor to the required position.
|
||||
///
|
||||
/// \param[in] p position to move to
|
||||
void
|
||||
TerminalDisplay::Move(Pos p) {
|
||||
Attach();
|
||||
|
@ -56,7 +56,8 @@ namespace textinput {
|
||||
Pos IndexToPos(size_t idx) const { return Pos(idx % fWidth, idx / fWidth); }
|
||||
size_t PosToIndex(const Pos& pos) const {
|
||||
// Convert a x|y position to an index.
|
||||
return pos.fCol + pos.fLine * fWidth; }
|
||||
return pos.fCol + pos.fLine * fWidth;
|
||||
}
|
||||
size_t GetWidth() const { return fWidth; }
|
||||
void SetWidth(size_t width) { fWidth = width; }
|
||||
|
||||
@ -66,10 +67,10 @@ namespace textinput {
|
||||
virtual void MoveLeft(size_t nCols = 1) = 0;
|
||||
virtual void MoveRight(size_t nCols = 1) = 0;
|
||||
virtual void MoveFront() = 0;
|
||||
size_t WriteWrapped(Range::EPromptUpdate PromptUpdate, bool hidden,
|
||||
size_t WriteWrapped(Range::EPromptUpdate PromptUpdate, bool masked,
|
||||
size_t offset, size_t len = (size_t)-1);
|
||||
size_t WriteWrappedElement(const Text& what, size_t TextOffset,
|
||||
size_t WriteOffset, size_t Requested);
|
||||
size_t WriteWrappedTextPart(const Text &text, size_t TextOffset,
|
||||
size_t WriteOffset, size_t Requested);
|
||||
virtual void SetColor(char CIdx, const Color& C) = 0;
|
||||
virtual void WriteRawString(const char* text, size_t len) = 0;
|
||||
virtual void ActOnEOL() {}
|
||||
@ -79,7 +80,7 @@ namespace textinput {
|
||||
protected:
|
||||
bool fIsTTY; // whether this is a terminal or redirected
|
||||
size_t fWidth; // Width of the terminal in character columns
|
||||
size_t fWriteLen; // Last char of output written.
|
||||
size_t fWriteLen; // Length of output written.
|
||||
Pos fWritePos; // Current position of writing (temporarily != cursor)
|
||||
char fPrevColor; // currently configured color
|
||||
};
|
||||
|
@ -229,13 +229,20 @@ namespace textinput {
|
||||
MoveInternal('D', nCols);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// Erases the input to the right of the cursor.
|
||||
void
|
||||
TerminalDisplayUnix::EraseToRight() {
|
||||
static const char text[] = {(char)0x1b, '[', 'K'};
|
||||
static const char text[] = {(char)0x1b, '[', 'K'}; // ESC[K
|
||||
if (!IsTTY()) return;
|
||||
WriteRawString(text, sizeof(text));
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// Writes out a raw string to stdout.
|
||||
///
|
||||
/// \param[in] text raw string to be written out
|
||||
/// \param[in] len length of the raw string
|
||||
void
|
||||
TerminalDisplayUnix::WriteRawString(const char *text, size_t len) {
|
||||
if (write(fileno(stdout), text, len) == -1) {
|
||||
@ -243,6 +250,9 @@ namespace textinput {
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// Invoke this on EOL. Writes out space backspace, to wrap to the next line.
|
||||
/// Otherwise, we stay on the same line and the input gets pushed upwards.
|
||||
void
|
||||
TerminalDisplayUnix::ActOnEOL() {
|
||||
if (!IsTTY()) return;
|
||||
|
@ -31,7 +31,7 @@
|
||||
namespace textinput {
|
||||
TextInput::TextInput(Reader& reader, Display& display,
|
||||
const char* HistFile /* = 0 */):
|
||||
fHidden(false),
|
||||
fMasked(false),
|
||||
fAutoHistAdd(true),
|
||||
fLastKey(0),
|
||||
fMaxChars(0),
|
||||
|
@ -47,7 +47,7 @@ namespace textinput {
|
||||
|
||||
// Getters
|
||||
const TextInputContext* GetContext() const { return fContext; }
|
||||
bool IsInputHidden() const { return fHidden; }
|
||||
bool IsInputMasked() const { return fMasked; }
|
||||
|
||||
size_t GetMaxPendingCharsToRead() const { return fMaxChars; }
|
||||
bool IsReadingAllPendingChars() const { return fMaxChars == (size_t) -1; }
|
||||
@ -55,7 +55,7 @@ namespace textinput {
|
||||
|
||||
// Setters
|
||||
void SetPrompt(const char* p);
|
||||
void HideInput(bool hidden = true) { fHidden = hidden; }
|
||||
void MaskInput(bool masked = true) { fMasked = masked; }
|
||||
void SetColorizer(Colorizer* c);
|
||||
void SetCompletion(TabCompletion* tc);
|
||||
void SetFunctionKeyHandler(FunKey* fc);
|
||||
@ -93,7 +93,7 @@ namespace textinput {
|
||||
void ProcessNewInput(const InputData& in, EditorRange& r);
|
||||
void DisplayNewInput(EditorRange& r, size_t& oldCursorPos);
|
||||
|
||||
bool fHidden; // whether input should be shown
|
||||
bool fMasked; // whether input should be shown
|
||||
bool fAutoHistAdd; // whether input should be added to history
|
||||
char fLastKey; // most recently read key
|
||||
size_t fMaxChars; // Num chars to read; 0 for blocking, -1 for all available
|
||||
|
@ -76,7 +76,7 @@ int main (int argc, const char * argv[]) {
|
||||
}
|
||||
TI.TakeInput(line);
|
||||
printf("INPUT: --BEGIN--%s--END--\n", line.c_str());
|
||||
if (line == "h") TI.HideInput(!TI.IsInputHidden());
|
||||
if (line == "h") TI.MaskInput(!TI.IsInputMasked());
|
||||
} while (!TI.AtEOF() && line != ".q");
|
||||
|
||||
delete R;
|
||||
|
Loading…
x
Reference in New Issue
Block a user