| //===-- Editline.cpp --------------------------------------------*- C++ -*-===// |
| // |
| // The LLVM Compiler Infrastructure |
| // |
| // This file is distributed under the University of Illinois Open Source |
| // License. See LICENSE.TXT for details. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| |
| #include "lldb/Host/Editline.h" |
| |
| #include "lldb/Core/Error.h" |
| #include "lldb/Core/StreamString.h" |
| #include "lldb/Core/StringList.h" |
| #include "lldb/Host/Host.h" |
| |
| #include <limits.h> |
| |
| using namespace lldb; |
| using namespace lldb_private; |
| |
| namespace lldb_private { |
| typedef std::weak_ptr<EditlineHistory> EditlineHistoryWP; |
| |
| |
| // EditlineHistory objects are sometimes shared between multiple |
| // Editline instances with the same program name. This class allows |
| // multiple editline instances to |
| // |
| |
| class EditlineHistory |
| { |
| private: |
| // Use static GetHistory() function to get a EditlineHistorySP to one of these objects |
| EditlineHistory(const std::string &prefix, uint32_t size, bool unique_entries) : |
| m_history (NULL), |
| m_event (), |
| m_prefix (prefix), |
| m_path () |
| { |
| m_history = ::history_init(); |
| ::history (m_history, &m_event, H_SETSIZE, size); |
| if (unique_entries) |
| ::history (m_history, &m_event, H_SETUNIQUE, 1); |
| } |
| |
| const char * |
| GetHistoryFilePath() |
| { |
| if (m_path.empty() && m_history && !m_prefix.empty()) |
| { |
| char history_path[PATH_MAX]; |
| ::snprintf (history_path, sizeof(history_path), "~/.%s-history", m_prefix.c_str()); |
| m_path = std::move(FileSpec(history_path, true).GetPath()); |
| } |
| if (m_path.empty()) |
| return NULL; |
| return m_path.c_str(); |
| } |
| |
| public: |
| |
| ~EditlineHistory() |
| { |
| Save (); |
| |
| if (m_history) |
| { |
| ::history_end (m_history); |
| m_history = NULL; |
| } |
| } |
| |
| static EditlineHistorySP |
| GetHistory (const std::string &prefix) |
| { |
| typedef std::map<std::string, EditlineHistoryWP> WeakHistoryMap; |
| static Mutex g_mutex(Mutex::eMutexTypeRecursive); |
| static WeakHistoryMap g_weak_map; |
| Mutex::Locker locker (g_mutex); |
| WeakHistoryMap::const_iterator pos = g_weak_map.find (prefix); |
| EditlineHistorySP history_sp; |
| if (pos != g_weak_map.end()) |
| { |
| history_sp = pos->second.lock(); |
| if (history_sp) |
| return history_sp; |
| g_weak_map.erase(pos); |
| } |
| history_sp.reset(new EditlineHistory(prefix, 800, true)); |
| g_weak_map[prefix] = history_sp; |
| return history_sp; |
| } |
| |
| bool IsValid() const |
| { |
| return m_history != NULL; |
| } |
| |
| ::History * |
| GetHistoryPtr () |
| { |
| return m_history; |
| } |
| |
| void |
| Enter (const char *line_cstr) |
| { |
| if (m_history) |
| ::history (m_history, &m_event, H_ENTER, line_cstr); |
| } |
| |
| bool |
| Load () |
| { |
| if (m_history) |
| { |
| const char *path = GetHistoryFilePath(); |
| if (path) |
| { |
| ::history (m_history, &m_event, H_LOAD, path); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool |
| Save () |
| { |
| if (m_history) |
| { |
| const char *path = GetHistoryFilePath(); |
| if (path) |
| { |
| ::history (m_history, &m_event, H_SAVE, path); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| protected: |
| ::History *m_history; // The history object |
| ::HistEvent m_event;// The history event needed to contain all history events |
| std::string m_prefix; // The prefix name (usually the editline program name) to use when loading/saving history |
| std::string m_path; // Path to the history file |
| }; |
| } |
| |
| |
| static const char k_prompt_escape_char = '\1'; |
| |
| Editline::Editline (const char *prog, // prog can't be NULL |
| const char *prompt, // can be NULL for no prompt |
| bool configure_for_multiline, |
| FILE *fin, |
| FILE *fout, |
| FILE *ferr) : |
| m_editline (NULL), |
| m_history_sp (), |
| m_prompt (), |
| m_lines_prompt (), |
| m_getting_char (false), |
| m_completion_callback (NULL), |
| m_completion_callback_baton (NULL), |
| m_line_complete_callback (NULL), |
| m_line_complete_callback_baton (NULL), |
| m_lines_command (Command::None), |
| m_line_offset (0), |
| m_lines_curr_line (0), |
| m_lines_max_line (0), |
| m_file (fileno(fin), false), |
| m_prompt_with_line_numbers (false), |
| m_getting_line (false), |
| m_got_eof (false), |
| m_interrupted (false) |
| { |
| if (prog && prog[0]) |
| { |
| m_editline = ::el_init(prog, fin, fout, ferr); |
| |
| // Get a shared history instance |
| m_history_sp = EditlineHistory::GetHistory(prog); |
| } |
| else |
| { |
| m_editline = ::el_init("lldb-tmp", fin, fout, ferr); |
| } |
| |
| if (prompt && prompt[0]) |
| SetPrompt (prompt); |
| |
| //::el_set (m_editline, EL_BIND, "^[[A", NULL); // Print binding for up arrow key |
| //::el_set (m_editline, EL_BIND, "^[[B", NULL); // Print binding for up down key |
| |
| assert (m_editline); |
| ::el_set (m_editline, EL_CLIENTDATA, this); |
| |
| // only defined for newer versions of editline |
| #ifdef EL_PROMPT_ESC |
| ::el_set (m_editline, EL_PROMPT_ESC, GetPromptCallback, k_prompt_escape_char); |
| #else |
| // fall back on old prompt setting code |
| ::el_set (m_editline, EL_PROMPT, GetPromptCallback); |
| #endif |
| ::el_set (m_editline, EL_EDITOR, "emacs"); |
| if (m_history_sp && m_history_sp->IsValid()) |
| { |
| ::el_set (m_editline, EL_HIST, history, m_history_sp->GetHistoryPtr()); |
| } |
| ::el_set (m_editline, EL_ADDFN, "lldb-complete", "Editline completion function", Editline::CallbackComplete); |
| // Keep old "lldb_complete" mapping for older clients that used this in their .editrc. editline also |
| // has a bad bug where if you have a bind command that tries to bind to a function name that doesn't |
| // exist, it will corrupt the heap and probably crash your process later. |
| ::el_set (m_editline, EL_ADDFN, "lldb_complete", "Editline completion function", Editline::CallbackComplete); |
| ::el_set (m_editline, EL_ADDFN, "lldb-edit-prev-line", "Editline edit prev line", Editline::CallbackEditPrevLine); |
| ::el_set (m_editline, EL_ADDFN, "lldb-edit-next-line", "Editline edit next line", Editline::CallbackEditNextLine); |
| |
| ::el_set (m_editline, EL_BIND, "^r", "em-inc-search-prev", NULL); // Cycle through backwards search, entering string |
| ::el_set (m_editline, EL_BIND, "^w", "ed-delete-prev-word", NULL); // Delete previous word, behave like bash does. |
| ::el_set (m_editline, EL_BIND, "\033[3~", "ed-delete-next-char", NULL); // Fix the delete key. |
| ::el_set (m_editline, EL_BIND, "\t", "lldb-complete", NULL); // Bind TAB to be auto complete |
| |
| if (configure_for_multiline) |
| { |
| // Use escape sequences for control characters due to bugs in editline |
| // where "-k up" and "-k down" don't always work. |
| ::el_set (m_editline, EL_BIND, "^[[A", "lldb-edit-prev-line", NULL); // Map up arrow |
| ::el_set (m_editline, EL_BIND, "^[[B", "lldb-edit-next-line", NULL); // Map down arrow |
| // Bindings for next/prev history |
| ::el_set (m_editline, EL_BIND, "^P", "ed-prev-history", NULL); // Map up arrow |
| ::el_set (m_editline, EL_BIND, "^N", "ed-next-history", NULL); // Map down arrow |
| } |
| else |
| { |
| // Use escape sequences for control characters due to bugs in editline |
| // where "-k up" and "-k down" don't always work. |
| ::el_set (m_editline, EL_BIND, "^[[A", "ed-prev-history", NULL); // Map up arrow |
| ::el_set (m_editline, EL_BIND, "^[[B", "ed-next-history", NULL); // Map down arrow |
| } |
| |
| // Source $PWD/.editrc then $HOME/.editrc |
| ::el_source (m_editline, NULL); |
| |
| // Always read through our callback function so we don't read |
| // stuff we aren't supposed to. This also stops the extra echoing |
| // that can happen when you have more input than editline can handle |
| // at once. |
| SetGetCharCallback(GetCharFromInputFileCallback); |
| |
| LoadHistory(); |
| } |
| |
| Editline::~Editline() |
| { |
| // EditlineHistory objects are sometimes shared between multiple |
| // Editline instances with the same program name. So just release |
| // our shared pointer and if we are the last owner, it will save the |
| // history to the history save file automatically. |
| m_history_sp.reset(); |
| |
| // Disable edit mode to stop the terminal from flushing all input |
| // during the call to el_end() since we expect to have multiple editline |
| // instances in this program. |
| ::el_set (m_editline, EL_EDITMODE, 0); |
| |
| ::el_end(m_editline); |
| m_editline = NULL; |
| } |
| |
| void |
| Editline::SetGetCharCallback (GetCharCallbackType callback) |
| { |
| ::el_set (m_editline, EL_GETCFN, callback); |
| } |
| |
| bool |
| Editline::LoadHistory () |
| { |
| if (m_history_sp) |
| return m_history_sp->Load(); |
| return false; |
| } |
| |
| bool |
| Editline::SaveHistory () |
| { |
| if (m_history_sp) |
| return m_history_sp->Save(); |
| return false; |
| } |
| |
| |
| Error |
| Editline::PrivateGetLine(std::string &line) |
| { |
| Error error; |
| if (m_interrupted) |
| { |
| error.SetErrorString("interrupted"); |
| return error; |
| } |
| |
| line.clear(); |
| if (m_editline != NULL) |
| { |
| int line_len = 0; |
| // Call el_gets to prompt the user and read the user's input. |
| const char *line_cstr = ::el_gets (m_editline, &line_len); |
| |
| static int save_errno = (line_len < 0) ? errno : 0; |
| |
| if (save_errno != 0) |
| { |
| error.SetError(save_errno, eErrorTypePOSIX); |
| } |
| else if (line_cstr) |
| { |
| // Decrement the length so we don't have newline characters in "line" for when |
| // we assign the cstr into the std::string |
| llvm::StringRef line_ref (line_cstr); |
| line_ref = line_ref.rtrim("\n\r"); |
| |
| if (!line_ref.empty() && !m_interrupted) |
| { |
| // We didn't strip the newlines, we just adjusted the length, and |
| // we want to add the history item with the newlines |
| if (m_history_sp) |
| m_history_sp->Enter(line_cstr); |
| |
| // Copy the part of the c string that we want (removing the newline chars) |
| line = std::move(line_ref.str()); |
| } |
| } |
| } |
| else |
| { |
| error.SetErrorString("the EditLine instance has been deleted"); |
| } |
| return error; |
| } |
| |
| |
| Error |
| Editline::GetLine(std::string &line, bool &interrupted) |
| { |
| Error error; |
| interrupted = false; |
| line.clear(); |
| |
| // Set arrow key bindings for up and down arrows for single line |
| // mode where up and down arrows do prev/next history |
| m_interrupted = false; |
| |
| if (!m_got_eof) |
| { |
| if (m_getting_line) |
| { |
| error.SetErrorString("already getting a line"); |
| return error; |
| } |
| if (m_lines_curr_line > 0) |
| { |
| error.SetErrorString("already getting lines"); |
| return error; |
| } |
| m_getting_line = true; |
| error = PrivateGetLine(line); |
| m_getting_line = false; |
| } |
| |
| interrupted = m_interrupted; |
| |
| if (m_got_eof && line.empty()) |
| { |
| // Only set the error if we didn't get an error back from PrivateGetLine() |
| if (error.Success()) |
| error.SetErrorString("end of file"); |
| } |
| |
| return error; |
| } |
| |
| size_t |
| Editline::Push (const char *bytes, size_t len) |
| { |
| if (m_editline) |
| { |
| // Must NULL terminate the string for el_push() so we stick it |
| // into a std::string first |
| ::el_push(m_editline, |
| const_cast<char*>(std::string (bytes, len).c_str())); |
| return len; |
| } |
| return 0; |
| } |
| |
| |
| Error |
| Editline::GetLines(const std::string &end_line, StringList &lines, bool &interrupted) |
| { |
| Error error; |
| interrupted = false; |
| if (m_getting_line) |
| { |
| error.SetErrorString("already getting a line"); |
| return error; |
| } |
| if (m_lines_curr_line > 0) |
| { |
| error.SetErrorString("already getting lines"); |
| return error; |
| } |
| |
| // Set arrow key bindings for up and down arrows for multiple line |
| // mode where up and down arrows do edit prev/next line |
| m_interrupted = false; |
| |
| LineStatus line_status = LineStatus::Success; |
| |
| lines.Clear(); |
| |
| FILE *out_file = GetOutputFile(); |
| FILE *err_file = GetErrorFile(); |
| m_lines_curr_line = 1; |
| while (line_status != LineStatus::Done) |
| { |
| const uint32_t line_idx = m_lines_curr_line-1; |
| if (line_idx >= lines.GetSize()) |
| lines.SetSize(m_lines_curr_line); |
| m_lines_max_line = lines.GetSize(); |
| m_lines_command = Command::None; |
| assert(line_idx < m_lines_max_line); |
| std::string &line = lines[line_idx]; |
| error = PrivateGetLine(line); |
| if (error.Fail()) |
| { |
| line_status = LineStatus::Error; |
| } |
| else if (m_interrupted) |
| { |
| interrupted = true; |
| line_status = LineStatus::Done; |
| } |
| else |
| { |
| switch (m_lines_command) |
| { |
| case Command::None: |
| if (m_line_complete_callback) |
| { |
| line_status = m_line_complete_callback (this, |
| lines, |
| line_idx, |
| error, |
| m_line_complete_callback_baton); |
| } |
| else if (line == end_line) |
| { |
| line_status = LineStatus::Done; |
| } |
| |
| if (line_status == LineStatus::Success) |
| { |
| ++m_lines_curr_line; |
| // If we already have content for the next line because |
| // we were editing previous lines, then populate the line |
| // with the appropriate contents |
| if (line_idx+1 < lines.GetSize() && !lines[line_idx+1].empty()) |
| ::el_push (m_editline, |
| const_cast<char*>(lines[line_idx+1].c_str())); |
| } |
| else if (line_status == LineStatus::Error) |
| { |
| // Clear to end of line ("ESC[K"), then print the error, |
| // then go to the next line ("\n") and then move cursor up |
| // two lines ("ESC[2A"). |
| fprintf (err_file, "\033[Kerror: %s\n\033[2A", error.AsCString()); |
| } |
| break; |
| case Command::EditPrevLine: |
| if (m_lines_curr_line > 1) |
| { |
| //::fprintf (out_file, "\033[1A\033[%uD\033[2K", (uint32_t)(m_lines_prompt.size() + lines[line_idx].size())); // Make cursor go up a line and clear that line |
| ::fprintf (out_file, "\033[1A\033[1000D\033[2K"); |
| if (!lines[line_idx-1].empty()) |
| ::el_push (m_editline, |
| const_cast<char*>(lines[line_idx-1].c_str())); |
| --m_lines_curr_line; |
| } |
| break; |
| case Command::EditNextLine: |
| // Allow the down arrow to create a new line |
| ++m_lines_curr_line; |
| //::fprintf (out_file, "\033[1B\033[%uD\033[2K", (uint32_t)(m_lines_prompt.size() + lines[line_idx].size())); |
| ::fprintf (out_file, "\033[1B\033[1000D\033[2K"); |
| if (line_idx+1 < lines.GetSize() && !lines[line_idx+1].empty()) |
| ::el_push (m_editline, |
| const_cast<char*>(lines[line_idx+1].c_str())); |
| break; |
| } |
| } |
| } |
| m_lines_curr_line = 0; |
| m_lines_command = Command::None; |
| |
| // If we have a callback, call it one more time to let the |
| // user know the lines are complete |
| if (m_line_complete_callback && !interrupted) |
| m_line_complete_callback (this, |
| lines, |
| UINT32_MAX, |
| error, |
| m_line_complete_callback_baton); |
| |
| return error; |
| } |
| |
| unsigned char |
| Editline::HandleCompletion (int ch) |
| { |
| if (m_completion_callback == NULL) |
| return CC_ERROR; |
| |
| const LineInfo *line_info = ::el_line(m_editline); |
| StringList completions; |
| int page_size = 40; |
| |
| const int num_completions = m_completion_callback (line_info->buffer, |
| line_info->cursor, |
| line_info->lastchar, |
| 0, // Don't skip any matches (start at match zero) |
| -1, // Get all the matches |
| completions, |
| m_completion_callback_baton); |
| |
| FILE *out_file = GetOutputFile(); |
| |
| // if (num_completions == -1) |
| // { |
| // ::el_insertstr (m_editline, m_completion_key); |
| // return CC_REDISPLAY; |
| // } |
| // else |
| if (num_completions == -2) |
| { |
| // Replace the entire line with the first string... |
| ::el_deletestr (m_editline, line_info->cursor - line_info->buffer); |
| ::el_insertstr (m_editline, completions.GetStringAtIndex(0)); |
| return CC_REDISPLAY; |
| } |
| |
| // If we get a longer match display that first. |
| const char *completion_str = completions.GetStringAtIndex(0); |
| if (completion_str != NULL && *completion_str != '\0') |
| { |
| el_insertstr (m_editline, completion_str); |
| return CC_REDISPLAY; |
| } |
| |
| if (num_completions > 1) |
| { |
| int num_elements = num_completions + 1; |
| ::fprintf (out_file, "\nAvailable completions:"); |
| if (num_completions < page_size) |
| { |
| for (int i = 1; i < num_elements; i++) |
| { |
| completion_str = completions.GetStringAtIndex(i); |
| ::fprintf (out_file, "\n\t%s", completion_str); |
| } |
| ::fprintf (out_file, "\n"); |
| } |
| else |
| { |
| int cur_pos = 1; |
| char reply; |
| int got_char; |
| while (cur_pos < num_elements) |
| { |
| int endpoint = cur_pos + page_size; |
| if (endpoint > num_elements) |
| endpoint = num_elements; |
| for (; cur_pos < endpoint; cur_pos++) |
| { |
| completion_str = completions.GetStringAtIndex(cur_pos); |
| ::fprintf (out_file, "\n\t%s", completion_str); |
| } |
| |
| if (cur_pos >= num_elements) |
| { |
| ::fprintf (out_file, "\n"); |
| break; |
| } |
| |
| ::fprintf (out_file, "\nMore (Y/n/a): "); |
| reply = 'n'; |
| got_char = el_getc(m_editline, &reply); |
| if (got_char == -1 || reply == 'n') |
| break; |
| if (reply == 'a') |
| page_size = num_elements - cur_pos; |
| } |
| } |
| |
| } |
| |
| if (num_completions == 0) |
| return CC_REFRESH_BEEP; |
| else |
| return CC_REDISPLAY; |
| } |
| |
| Editline * |
| Editline::GetClientData (::EditLine *e) |
| { |
| Editline *editline = NULL; |
| if (e && ::el_get(e, EL_CLIENTDATA, &editline) == 0) |
| return editline; |
| return NULL; |
| } |
| |
| FILE * |
| Editline::GetInputFile () |
| { |
| return GetFilePointer (m_editline, 0); |
| } |
| |
| FILE * |
| Editline::GetOutputFile () |
| { |
| return GetFilePointer (m_editline, 1); |
| } |
| |
| FILE * |
| Editline::GetErrorFile () |
| { |
| return GetFilePointer (m_editline, 2); |
| } |
| |
| const char * |
| Editline::GetPrompt() |
| { |
| if (m_prompt_with_line_numbers && m_lines_curr_line > 0) |
| { |
| StreamString strm; |
| strm.Printf("%3u: ", m_lines_curr_line); |
| m_lines_prompt = std::move(strm.GetString()); |
| return m_lines_prompt.c_str(); |
| } |
| else |
| { |
| return m_prompt.c_str(); |
| } |
| } |
| |
| void |
| Editline::SetPrompt (const char *p) |
| { |
| if (p && p[0]) |
| m_prompt = p; |
| else |
| m_prompt.clear(); |
| size_t start_pos = 0; |
| size_t escape_pos; |
| while ((escape_pos = m_prompt.find('\033', start_pos)) != std::string::npos) |
| { |
| m_prompt.insert(escape_pos, 1, k_prompt_escape_char); |
| start_pos += 2; |
| } |
| } |
| |
| FILE * |
| Editline::GetFilePointer (::EditLine *e, int fd) |
| { |
| FILE *file_ptr = NULL; |
| if (e && ::el_get(e, EL_GETFP, fd, &file_ptr) == 0) |
| return file_ptr; |
| return NULL; |
| } |
| |
| unsigned char |
| Editline::CallbackEditPrevLine (::EditLine *e, int ch) |
| { |
| Editline *editline = GetClientData (e); |
| if (editline->m_lines_curr_line > 1) |
| { |
| editline->m_lines_command = Command::EditPrevLine; |
| return CC_NEWLINE; |
| } |
| return CC_ERROR; |
| } |
| unsigned char |
| Editline::CallbackEditNextLine (::EditLine *e, int ch) |
| { |
| Editline *editline = GetClientData (e); |
| if (editline->m_lines_curr_line < editline->m_lines_max_line) |
| { |
| editline->m_lines_command = Command::EditNextLine; |
| return CC_NEWLINE; |
| } |
| return CC_ERROR; |
| } |
| |
| unsigned char |
| Editline::CallbackComplete (::EditLine *e, int ch) |
| { |
| Editline *editline = GetClientData (e); |
| if (editline) |
| return editline->HandleCompletion (ch); |
| return CC_ERROR; |
| } |
| |
| const char * |
| Editline::GetPromptCallback (::EditLine *e) |
| { |
| Editline *editline = GetClientData (e); |
| if (editline) |
| return editline->GetPrompt(); |
| return ""; |
| } |
| |
| int |
| Editline::GetCharFromInputFileCallback (EditLine *e, char *c) |
| { |
| Editline *editline = GetClientData (e); |
| if (editline && editline->m_got_eof == false) |
| { |
| FILE *f = editline->GetInputFile(); |
| if (f == NULL) |
| { |
| editline->m_got_eof = true; |
| return 0; |
| } |
| |
| |
| while (1) |
| { |
| lldb::ConnectionStatus status = eConnectionStatusSuccess; |
| char ch = 0; |
| // When we start to call el_gets() the editline library needs to |
| // output the prompt |
| editline->m_getting_char.SetValue(true, eBroadcastAlways); |
| const size_t n = editline->m_file.Read(&ch, 1, UINT32_MAX, status, NULL); |
| editline->m_getting_char.SetValue(false, eBroadcastAlways); |
| if (n) |
| { |
| if (ch == '\x04') |
| { |
| // Only turn a CTRL+D into a EOF if we receive the |
| // CTRL+D an empty line, otherwise it will forward |
| // delete the character at the cursor |
| const LineInfo *line_info = ::el_line(e); |
| if (line_info != NULL && |
| line_info->buffer == line_info->cursor && |
| line_info->cursor == line_info->lastchar) |
| { |
| editline->m_got_eof = true; |
| break; |
| } |
| } |
| |
| if (status == eConnectionStatusEndOfFile) |
| { |
| editline->m_got_eof = true; |
| break; |
| } |
| else |
| { |
| *c = ch; |
| return 1; |
| } |
| } |
| else |
| { |
| switch (status) |
| { |
| case eConnectionStatusInterrupted: |
| editline->m_interrupted = true; |
| *c = '\n'; |
| return 1; |
| |
| case eConnectionStatusSuccess: // Success |
| break; |
| |
| case eConnectionStatusError: // Check GetError() for details |
| case eConnectionStatusTimedOut: // Request timed out |
| case eConnectionStatusEndOfFile: // End-of-file encountered |
| case eConnectionStatusNoConnection: // No connection |
| case eConnectionStatusLostConnection: // Lost connection while connected to a valid connection |
| editline->m_got_eof = true; |
| break; |
| } |
| } |
| } |
| } |
| return 0; |
| } |
| |
| void |
| Editline::Hide () |
| { |
| if (m_getting_line) |
| { |
| // If we are getting a line, we might have started to call el_gets() and |
| // it might be printing the prompt. Here we make sure we are actually getting |
| // a character. This way we know the entire prompt has been printed. |
| TimeValue timeout = TimeValue::Now(); |
| timeout.OffsetWithSeconds(1); |
| if (m_getting_char.WaitForValueEqualTo(true, &timeout)) |
| { |
| FILE *out_file = GetOutputFile(); |
| if (out_file) |
| { |
| const LineInfo *line_info = ::el_line(m_editline); |
| if (line_info) |
| ::fprintf (out_file, "\033[%uD\033[K", (uint32_t)(strlen(GetPrompt()) + line_info->cursor - line_info->buffer)); |
| } |
| } |
| } |
| } |
| |
| |
| void |
| Editline::Refresh() |
| { |
| if (m_getting_line) |
| { |
| // If we are getting a line, we might have started to call el_gets() and |
| // it might be printing the prompt. Here we make sure we are actually getting |
| // a character. This way we know the entire prompt has been printed. |
| TimeValue timeout = TimeValue::Now(); |
| timeout.OffsetWithSeconds(1); |
| if (m_getting_char.WaitForValueEqualTo(true, &timeout)) |
| { |
| ::el_set (m_editline, EL_REFRESH); |
| } |
| } |
| } |
| |
| bool |
| Editline::Interrupt () |
| { |
| m_interrupted = true; |
| if (m_getting_line || m_lines_curr_line > 0) |
| return m_file.InterruptRead(); |
| return false; // Interrupt not handled as we weren't getting a line or lines |
| } |