123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889 |
- /*
- Minetest
- Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU Lesser General Public License as published by
- the Free Software Foundation; either version 2.1 of the License, or
- (at your option) any later version.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Lesser General Public License for more details.
- You should have received a copy of the GNU Lesser General Public License along
- with this program; if not, write to the Free Software Foundation, Inc.,
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
- #include "chat.h"
- #include <algorithm>
- #include <cctype>
- #include <sstream>
- #include "config.h"
- #include "debug.h"
- #include "util/strfnd.h"
- #include "util/string.h"
- #include "util/numeric.h"
- ChatBuffer::ChatBuffer(u32 scrollback):
- m_scrollback(scrollback)
- {
- if (m_scrollback == 0)
- m_scrollback = 1;
- m_empty_formatted_line.first = true;
- m_cache_clickable_chat_weblinks = false;
- // Curses mode cannot access g_settings here
- if (g_settings != nullptr) {
- m_cache_clickable_chat_weblinks = g_settings->getBool("clickable_chat_weblinks");
- if (m_cache_clickable_chat_weblinks) {
- std::string colorval = g_settings->get("chat_weblink_color");
- parseColorString(colorval, m_cache_chat_weblink_color, false, 255);
- m_cache_chat_weblink_color.setAlpha(255);
- }
- }
- }
- void ChatBuffer::addLine(const std::wstring &name, const std::wstring &text)
- {
- m_lines_modified = true;
- ChatLine line(name, text);
- m_unformatted.push_back(line);
- if (m_rows > 0) {
- // m_formatted is valid and must be kept valid
- bool scrolled_at_bottom = (m_scroll == getBottomScrollPos());
- u32 num_added = formatChatLine(line, m_cols, m_formatted);
- if (scrolled_at_bottom)
- m_scroll += num_added;
- }
- // Limit number of lines by m_scrollback
- if (m_unformatted.size() > m_scrollback) {
- deleteOldest(m_unformatted.size() - m_scrollback);
- }
- }
- void ChatBuffer::clear()
- {
- m_unformatted.clear();
- m_formatted.clear();
- m_scroll = 0;
- m_lines_modified = true;
- }
- u32 ChatBuffer::getLineCount() const
- {
- return m_unformatted.size();
- }
- const ChatLine& ChatBuffer::getLine(u32 index) const
- {
- assert(index < getLineCount()); // pre-condition
- return m_unformatted[index];
- }
- void ChatBuffer::step(f32 dtime)
- {
- for (ChatLine &line : m_unformatted) {
- line.age += dtime;
- }
- }
- void ChatBuffer::deleteOldest(u32 count)
- {
- bool at_bottom = (m_scroll == getBottomScrollPos());
- u32 del_unformatted = 0;
- u32 del_formatted = 0;
- while (count > 0 && del_unformatted < m_unformatted.size()) {
- ++del_unformatted;
- // keep m_formatted in sync
- if (del_formatted < m_formatted.size()) {
- sanity_check(m_formatted[del_formatted].first);
- ++del_formatted;
- while (del_formatted < m_formatted.size() &&
- !m_formatted[del_formatted].first)
- ++del_formatted;
- }
- --count;
- }
- m_unformatted.erase(m_unformatted.begin(), m_unformatted.begin() + del_unformatted);
- m_formatted.erase(m_formatted.begin(), m_formatted.begin() + del_formatted);
- if (del_unformatted > 0)
- m_lines_modified = true;
- if (at_bottom)
- m_scroll = getBottomScrollPos();
- else
- scrollAbsolute(m_scroll - del_formatted);
- }
- void ChatBuffer::deleteByAge(f32 maxAge)
- {
- u32 count = 0;
- while (count < m_unformatted.size() && m_unformatted[count].age > maxAge)
- ++count;
- deleteOldest(count);
- }
- u32 ChatBuffer::getRows() const
- {
- return m_rows;
- }
- void ChatBuffer::reformat(u32 cols, u32 rows)
- {
- if (cols == 0 || rows == 0)
- {
- // Clear formatted buffer
- m_cols = 0;
- m_rows = 0;
- m_scroll = 0;
- m_formatted.clear();
- }
- else if (cols != m_cols || rows != m_rows)
- {
- // TODO: Avoid reformatting ALL lines (even invisible ones)
- // each time the console size changes.
- // Find out the scroll position in *unformatted* lines
- u32 restore_scroll_unformatted = 0;
- u32 restore_scroll_formatted = 0;
- bool at_bottom = (m_scroll == getBottomScrollPos());
- if (!at_bottom)
- {
- for (s32 i = 0; i < m_scroll; ++i)
- {
- if (m_formatted[i].first)
- ++restore_scroll_unformatted;
- }
- }
- // If number of columns change, reformat everything
- if (cols != m_cols)
- {
- m_formatted.clear();
- for (u32 i = 0; i < m_unformatted.size(); ++i)
- {
- if (i == restore_scroll_unformatted)
- restore_scroll_formatted = m_formatted.size();
- formatChatLine(m_unformatted[i], cols, m_formatted);
- }
- }
- // Update the console size
- m_cols = cols;
- m_rows = rows;
- // Restore the scroll position
- if (at_bottom)
- {
- scrollBottom();
- }
- else
- {
- scrollAbsolute(restore_scroll_formatted);
- }
- }
- }
- const ChatFormattedLine& ChatBuffer::getFormattedLine(u32 row) const
- {
- s32 index = m_scroll + (s32) row;
- if (index >= 0 && index < (s32) m_formatted.size())
- return m_formatted[index];
- return m_empty_formatted_line;
- }
- void ChatBuffer::scroll(s32 rows)
- {
- scrollAbsolute(m_scroll + rows);
- }
- void ChatBuffer::scrollAbsolute(s32 scroll)
- {
- s32 top = getTopScrollPos();
- s32 bottom = getBottomScrollPos();
- m_scroll = scroll;
- if (m_scroll < top)
- m_scroll = top;
- if (m_scroll > bottom)
- m_scroll = bottom;
- }
- void ChatBuffer::scrollBottom()
- {
- m_scroll = getBottomScrollPos();
- }
- u32 ChatBuffer::formatChatLine(const ChatLine& line, u32 cols,
- std::vector<ChatFormattedLine>& destination) const
- {
- u32 num_added = 0;
- std::vector<ChatFormattedFragment> next_frags;
- ChatFormattedLine next_line;
- ChatFormattedFragment temp_frag;
- u32 out_column = 0;
- u32 in_pos = 0;
- u32 hanging_indentation = 0;
- // Format the sender name and produce fragments
- if (!line.name.empty()) {
- temp_frag.text = L"<";
- temp_frag.column = 0;
- //temp_frag.bold = 0;
- next_frags.push_back(temp_frag);
- temp_frag.text = line.name;
- temp_frag.column = 0;
- //temp_frag.bold = 1;
- next_frags.push_back(temp_frag);
- temp_frag.text = L"> ";
- temp_frag.column = 0;
- //temp_frag.bold = 0;
- next_frags.push_back(temp_frag);
- }
- std::wstring name_sanitized = line.name.c_str();
- // Choose an indentation level
- if (line.name.empty()) {
- // Server messages
- hanging_indentation = 0;
- } else if (name_sanitized.size() + 3 <= cols/2) {
- // Names shorter than about half the console width
- hanging_indentation = line.name.size() + 3;
- } else {
- // Very long names
- hanging_indentation = 2;
- }
- //EnrichedString line_text(line.text);
- next_line.first = true;
- // Set/use forced newline after the last frag in each line
- bool mark_newline = false;
- // Produce fragments and layout them into lines
- while (!next_frags.empty() || in_pos < line.text.size()) {
- mark_newline = false; // now using this to USE line-end frag
- // Layout fragments into lines
- while (!next_frags.empty()) {
- ChatFormattedFragment& frag = next_frags[0];
- // Force newline after this frag, if marked
- if (frag.column == INT_MAX)
- mark_newline = true;
- if (frag.text.size() <= cols - out_column) {
- // Fragment fits into current line
- frag.column = out_column;
- next_line.fragments.push_back(frag);
- out_column += frag.text.size();
- next_frags.erase(next_frags.begin());
- } else {
- // Fragment does not fit into current line
- // So split it up
- temp_frag.text = frag.text.substr(0, cols - out_column);
- temp_frag.column = out_column;
- temp_frag.weblink = frag.weblink;
- next_line.fragments.push_back(temp_frag);
- frag.text = frag.text.substr(cols - out_column);
- frag.column = 0;
- out_column = cols;
- }
- if (out_column == cols || mark_newline) {
- // End the current line
- destination.push_back(next_line);
- num_added++;
- next_line.fragments.clear();
- next_line.first = false;
- out_column = hanging_indentation;
- mark_newline = false;
- }
- }
- // Produce fragment(s) for next formatted line
- if (!(in_pos < line.text.size()))
- continue;
- const std::wstring &linestring = line.text.getString();
- u32 remaining_in_output = cols - out_column;
- size_t http_pos = std::wstring::npos;
- mark_newline = false; // now using this to SET line-end frag
- // Construct all frags for next output line
- while (!mark_newline) {
- // Determine a fragment length <= the minimum of
- // remaining_in_{in,out}put. Try to end the fragment
- // on a word boundary.
- u32 frag_length = 0, space_pos = 0;
- u32 remaining_in_input = line.text.size() - in_pos;
- if (m_cache_clickable_chat_weblinks) {
- // Note: unsigned(-1) on fail
- http_pos = linestring.find(L"https://", in_pos);
- if (http_pos == std::wstring::npos)
- http_pos = linestring.find(L"http://", in_pos);
- if (http_pos != std::wstring::npos)
- http_pos -= in_pos;
- }
- while (frag_length < remaining_in_input &&
- frag_length < remaining_in_output) {
- if (iswspace(linestring[in_pos + frag_length]))
- space_pos = frag_length;
- ++frag_length;
- }
- if (http_pos >= remaining_in_output) {
- // Http not in range, grab until space or EOL, halt as normal.
- // Note this works because (http_pos = npos) is unsigned(-1)
- mark_newline = true;
- } else if (http_pos == 0) {
- // At http, grab ALL until FIRST whitespace or end marker. loop.
- // If at end of string, next loop will be empty string to mark end of weblink.
- frag_length = 6; // Frag is at least "http://"
- // Chars to mark end of weblink
- // TODO? replace this with a safer (slower) regex whitelist?
- static const std::wstring delim_chars = L"\'\";";
- wchar_t tempchar = linestring[in_pos+frag_length];
- while (frag_length < remaining_in_input &&
- !iswspace(tempchar) &&
- delim_chars.find(tempchar) == std::wstring::npos) {
- ++frag_length;
- tempchar = linestring[in_pos+frag_length];
- }
- space_pos = frag_length - 1;
- // This frag may need to be force-split. That's ok, urls aren't "words"
- if (frag_length >= remaining_in_output) {
- mark_newline = true;
- }
- } else {
- // Http in range, grab until http, loop
- space_pos = http_pos - 1;
- frag_length = http_pos;
- }
- // Include trailing space in current frag
- if (space_pos != 0 && frag_length < remaining_in_input)
- frag_length = space_pos + 1;
- temp_frag.text = line.text.substr(in_pos, frag_length);
- // A hack so this frag remembers mark_newline for the layout phase
- temp_frag.column = mark_newline ? INT_MAX : 0;
- if (http_pos == 0) {
- // Discard color stuff from the source frag
- temp_frag.text = EnrichedString(temp_frag.text.getString());
- temp_frag.text.setDefaultColor(m_cache_chat_weblink_color);
- // Set weblink in the frag meta
- temp_frag.weblink = wide_to_utf8(temp_frag.text.getString());
- } else {
- temp_frag.weblink.clear();
- }
- next_frags.push_back(temp_frag);
- in_pos += frag_length;
- remaining_in_output -= std::min(frag_length, remaining_in_output);
- }
- }
- // End the last line
- if (num_added == 0 || !next_line.fragments.empty()) {
- destination.push_back(next_line);
- num_added++;
- }
- return num_added;
- }
- s32 ChatBuffer::getTopScrollPos() const
- {
- s32 formatted_count = (s32) m_formatted.size();
- s32 rows = (s32) m_rows;
- if (rows == 0)
- return 0;
- if (formatted_count <= rows)
- return formatted_count - rows;
- return 0;
- }
- s32 ChatBuffer::getBottomScrollPos() const
- {
- s32 formatted_count = (s32) m_formatted.size();
- s32 rows = (s32) m_rows;
- if (rows == 0)
- return 0;
- return formatted_count - rows;
- }
- void ChatBuffer::resize(u32 scrollback)
- {
- m_scrollback = scrollback;
- if (m_unformatted.size() > m_scrollback)
- deleteOldest(m_unformatted.size() - m_scrollback);
- }
- ChatPrompt::ChatPrompt(const std::wstring &prompt, u32 history_limit):
- m_prompt(prompt),
- m_history_limit(history_limit)
- {
- }
- const std::wstring &ChatPrompt::getLineRef() const
- {
- return m_history_index >= m_history.size() ? m_line : m_history[m_history_index].line;
- }
- std::wstring &ChatPrompt::makeLineRef()
- {
- if (m_history_index >= m_history.size()) {
- return m_line;
- } else {
- if (!m_history[m_history_index].saved)
- m_history[m_history_index].saved = m_history[m_history_index].line;
- return m_history[m_history_index].line;
- }
- }
- bool ChatPrompt::HistoryEntry::operator==(const ChatPrompt::HistoryEntry &other)
- {
- if (line != other.line)
- return false;
- if (saved == other.saved)
- return true;
- if ((!saved || saved == line) && (!other.saved || other.saved == other.line))
- return true;
- return false;
- }
- void ChatPrompt::input(wchar_t ch)
- {
- makeLineRef().insert(m_cursor, 1, ch);
- m_cursor++;
- clampView();
- m_nick_completion_start = 0;
- m_nick_completion_end = 0;
- }
- void ChatPrompt::input(const std::wstring &str)
- {
- makeLineRef().insert(m_cursor, str);
- m_cursor += str.size();
- clampView();
- m_nick_completion_start = 0;
- m_nick_completion_end = 0;
- }
- void ChatPrompt::addToHistory(const std::wstring &line)
- {
- std::wstring old_line = getLine();
- if (m_history_index < m_history.size()) {
- auto entry = m_history.begin() + m_history_index;
- if (entry->saved && entry->line == line) {
- entry->line = *entry->saved;
- entry->saved = nullopt;
- // Remove potential duplicates
- auto dup_before = std::find(m_history.begin(), entry, *entry);
- if (dup_before != entry)
- m_history.erase(dup_before);
- else if (std::find(entry + 1, m_history.end(), *entry) != m_history.end())
- m_history.erase(entry);
- }
- }
- if (!line.empty() &&
- (m_history.size() == 0 || m_history.back().line != line)) {
- HistoryEntry entry(line);
- // Remove all duplicates
- m_history.erase(std::remove(m_history.begin(), m_history.end(), entry),
- m_history.end());
- // Push unique line
- m_history.push_back(std::move(entry));
- }
- if (m_history.size() > m_history_limit)
- m_history.erase(m_history.begin());
- m_history_index = m_history.size();
- m_line = std::move(old_line);
- }
- void ChatPrompt::clear()
- {
- makeLineRef().clear();
- m_view = 0;
- m_cursor = 0;
- m_nick_completion_start = 0;
- m_nick_completion_end = 0;
- }
- std::wstring ChatPrompt::replace(const std::wstring &line)
- {
- std::wstring old_line = getLine();
- makeLineRef() = line;
- m_view = m_cursor = line.size();
- clampView();
- m_nick_completion_start = 0;
- m_nick_completion_end = 0;
- return old_line;
- }
- void ChatPrompt::historyPrev()
- {
- if (m_history_index != 0) {
- --m_history_index;
- m_view = m_cursor = getLineRef().size();
- clampView();
- m_nick_completion_start = 0;
- m_nick_completion_end = 0;
- }
- }
- void ChatPrompt::historyNext()
- {
- if (m_history_index < m_history.size()) {
- m_history_index++;
- m_view = m_cursor = getLineRef().size();
- clampView();
- m_nick_completion_start = 0;
- m_nick_completion_end = 0;
- }
- }
- void ChatPrompt::nickCompletion(const std::list<std::string>& names, bool backwards)
- {
- // Two cases:
- // (a) m_nick_completion_start == m_nick_completion_end == 0
- // Then no previous nick completion is active.
- // Get the word around the cursor and replace with any nick
- // that has that word as a prefix.
- // (b) else, continue a previous nick completion.
- // m_nick_completion_start..m_nick_completion_end are the
- // interval where the originally used prefix was. Cycle
- // through the list of completions of that prefix.
- const std::wstring &line = getLineRef();
- u32 prefix_start = m_nick_completion_start;
- u32 prefix_end = m_nick_completion_end;
- bool initial = (prefix_end == 0);
- if (initial)
- {
- // no previous nick completion is active
- prefix_start = prefix_end = m_cursor;
- while (prefix_start > 0 && !iswspace(line[prefix_start-1]))
- --prefix_start;
- while (prefix_end < line.size() && !iswspace(line[prefix_end]))
- ++prefix_end;
- if (prefix_start == prefix_end)
- return;
- }
- std::wstring prefix = line.substr(prefix_start, prefix_end - prefix_start);
- // find all names that start with the selected prefix
- std::vector<std::wstring> completions;
- for (const std::string &name : names) {
- std::wstring completion = utf8_to_wide(name);
- if (str_starts_with(completion, prefix, true)) {
- if (prefix_start == 0)
- completion += L": ";
- completions.push_back(completion);
- }
- }
- if (completions.empty())
- return;
- // find a replacement string and the word that will be replaced
- u32 word_end = prefix_end;
- u32 replacement_index = 0;
- if (!initial)
- {
- while (word_end < line.size() && !iswspace(line[word_end]))
- ++word_end;
- std::wstring word = line.substr(prefix_start, word_end - prefix_start);
- // cycle through completions
- for (u32 i = 0; i < completions.size(); ++i)
- {
- if (str_equal(word, completions[i], true))
- {
- if (backwards)
- replacement_index = i + completions.size() - 1;
- else
- replacement_index = i + 1;
- replacement_index %= completions.size();
- break;
- }
- }
- }
- std::wstring replacement = completions[replacement_index];
- if (word_end < line.size() && iswspace(line[word_end]))
- ++word_end;
- // replace existing word with replacement word,
- // place the cursor at the end and record the completion prefix
- makeLineRef().replace(prefix_start, word_end - prefix_start, replacement);
- m_cursor = prefix_start + replacement.size();
- clampView();
- m_nick_completion_start = prefix_start;
- m_nick_completion_end = prefix_end;
- }
- void ChatPrompt::reformat(u32 cols)
- {
- if (cols <= m_prompt.size())
- {
- m_cols = 0;
- m_view = m_cursor;
- }
- else
- {
- s32 length = getLineRef().size();
- bool was_at_end = (m_view + m_cols >= length + 1);
- m_cols = cols - m_prompt.size();
- if (was_at_end)
- m_view = length;
- clampView();
- }
- }
- std::wstring ChatPrompt::getVisiblePortion() const
- {
- return m_prompt + getLineRef().substr(m_view, m_cols);
- }
- s32 ChatPrompt::getVisibleCursorPosition() const
- {
- return m_cursor - m_view + m_prompt.size();
- }
- void ChatPrompt::cursorOperation(CursorOp op, CursorOpDir dir, CursorOpScope scope)
- {
- s32 old_cursor = m_cursor;
- s32 new_cursor = m_cursor;
- const std::wstring &line = getLineRef();
- s32 length = line.size();
- s32 increment = (dir == CURSOROP_DIR_RIGHT) ? 1 : -1;
- switch (scope) {
- case CURSOROP_SCOPE_CHARACTER:
- new_cursor += increment;
- break;
- case CURSOROP_SCOPE_WORD:
- if (dir == CURSOROP_DIR_RIGHT) {
- // skip one word to the right
- while (new_cursor < length && iswspace(line[new_cursor]))
- new_cursor++;
- while (new_cursor < length && !iswspace(line[new_cursor]))
- new_cursor++;
- while (new_cursor < length && iswspace(line[new_cursor]))
- new_cursor++;
- } else {
- // skip one word to the left
- while (new_cursor >= 1 && iswspace(line[new_cursor - 1]))
- new_cursor--;
- while (new_cursor >= 1 && !iswspace(line[new_cursor - 1]))
- new_cursor--;
- }
- break;
- case CURSOROP_SCOPE_LINE:
- new_cursor += increment * length;
- break;
- case CURSOROP_SCOPE_SELECTION:
- break;
- }
- new_cursor = MYMAX(MYMIN(new_cursor, length), 0);
- switch (op) {
- case CURSOROP_MOVE:
- m_cursor = new_cursor;
- m_cursor_len = 0;
- break;
- case CURSOROP_DELETE:
- if (m_cursor_len > 0) { // Delete selected text first
- makeLineRef().erase(m_cursor, m_cursor_len);
- } else {
- m_cursor = MYMIN(new_cursor, old_cursor);
- makeLineRef().erase(m_cursor, abs(new_cursor - old_cursor));
- }
- m_cursor_len = 0;
- break;
- case CURSOROP_SELECT:
- if (scope == CURSOROP_SCOPE_LINE) {
- m_cursor = 0;
- m_cursor_len = length;
- } else {
- m_cursor = MYMIN(new_cursor, old_cursor);
- m_cursor_len += abs(new_cursor - old_cursor);
- m_cursor_len = MYMIN(m_cursor_len, length - m_cursor);
- }
- break;
- }
- clampView();
- m_nick_completion_start = 0;
- m_nick_completion_end = 0;
- }
- void ChatPrompt::clampView()
- {
- s32 length = getLineRef().size();
- if (length + 1 <= m_cols)
- {
- m_view = 0;
- }
- else
- {
- m_view = MYMIN(m_view, length + 1 - m_cols);
- m_view = MYMIN(m_view, m_cursor);
- m_view = MYMAX(m_view, m_cursor - m_cols + 1);
- m_view = MYMAX(m_view, 0);
- }
- }
- ChatBackend::ChatBackend():
- m_console_buffer(500),
- m_recent_buffer(6),
- m_prompt(L"]", 500)
- {
- }
- void ChatBackend::addMessage(const std::wstring &name, std::wstring text)
- {
- // Note: A message may consist of multiple lines, for example the MOTD.
- text = translate_string(text);
- WStrfnd fnd(text);
- while (!fnd.at_end())
- {
- std::wstring line = fnd.next(L"\n");
- m_console_buffer.addLine(name, line);
- m_recent_buffer.addLine(name, line);
- }
- }
- void ChatBackend::addUnparsedMessage(std::wstring message)
- {
- // TODO: Remove the need to parse chat messages client-side, by sending
- // separate name and text fields in TOCLIENT_CHAT_MESSAGE.
- if (message.size() >= 2 && message[0] == L'<')
- {
- std::size_t closing = message.find_first_of(L'>', 1);
- if (closing != std::wstring::npos &&
- closing + 2 <= message.size() &&
- message[closing+1] == L' ')
- {
- std::wstring name = message.substr(1, closing - 1);
- std::wstring text = message.substr(closing + 2);
- addMessage(name, text);
- return;
- }
- }
- // Unable to parse, probably a server message.
- addMessage(L"", message);
- }
- ChatBuffer& ChatBackend::getConsoleBuffer()
- {
- return m_console_buffer;
- }
- ChatBuffer& ChatBackend::getRecentBuffer()
- {
- return m_recent_buffer;
- }
- EnrichedString ChatBackend::getRecentChat() const
- {
- EnrichedString result;
- for (u32 i = 0; i < m_recent_buffer.getLineCount(); ++i) {
- const ChatLine& line = m_recent_buffer.getLine(i);
- if (i != 0)
- result += L"\n";
- if (!line.name.empty()) {
- result += L"<";
- result += line.name;
- result += L"> ";
- }
- result += line.text;
- }
- return result;
- }
- ChatPrompt& ChatBackend::getPrompt()
- {
- return m_prompt;
- }
- void ChatBackend::reformat(u32 cols, u32 rows)
- {
- m_console_buffer.reformat(cols, rows);
- // no need to reformat m_recent_buffer, its formatted lines
- // are not used
- m_prompt.reformat(cols);
- }
- void ChatBackend::clearRecentChat()
- {
- m_recent_buffer.clear();
- }
- void ChatBackend::applySettings()
- {
- u32 recent_lines = g_settings->getU32("recent_chat_messages");
- recent_lines = rangelim(recent_lines, 2, 20);
- m_recent_buffer.resize(recent_lines);
- }
- void ChatBackend::step(float dtime)
- {
- m_recent_buffer.step(dtime);
- m_recent_buffer.deleteByAge(60.0);
- // no need to age messages in anything but m_recent_buffer
- }
- void ChatBackend::scroll(s32 rows)
- {
- m_console_buffer.scroll(rows);
- }
- void ChatBackend::scrollPageDown()
- {
- m_console_buffer.scroll(m_console_buffer.getRows());
- }
- void ChatBackend::scrollPageUp()
- {
- m_console_buffer.scroll(-(s32)m_console_buffer.getRows());
- }
|