Comments and refactorings (hidden -> masked) in the TextInput.

This commit is contained in:
Boris Perovic 2015-09-01 12:33:43 +02:00 committed by Axel Naumann
parent 515944b3ac
commit 60660df4b7
9 changed files with 148 additions and 62 deletions

View File

@ -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());

View File

@ -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
//===----------------------------------------------------------------------===//

View File

@ -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;

View File

@ -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();

View File

@ -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
};

View File

@ -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;

View File

@ -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),

View File

@ -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

View File

@ -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;