LCOV - code coverage report
Current view: top level - src - chat.cpp (source / functions) Hit Total Coverage
Test: report Lines: 93 406 22.9 %
Date: 2015-07-11 18:23:49 Functions: 24 52 46.2 %

          Line data    Source code
       1             : /*
       2             : Minetest
       3             : Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
       4             : 
       5             : This program is free software; you can redistribute it and/or modify
       6             : it under the terms of the GNU Lesser General Public License as published by
       7             : the Free Software Foundation; either version 2.1 of the License, or
       8             : (at your option) any later version.
       9             : 
      10             : This program is distributed in the hope that it will be useful,
      11             : but WITHOUT ANY WARRANTY; without even the implied warranty of
      12             : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      13             : GNU Lesser General Public License for more details.
      14             : 
      15             : You should have received a copy of the GNU Lesser General Public License along
      16             : with this program; if not, write to the Free Software Foundation, Inc.,
      17             : 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
      18             : */
      19             : 
      20             : #include "chat.h"
      21             : #include "debug.h"
      22             : #include "strfnd.h"
      23             : #include <cctype>
      24             : #include <sstream>
      25             : #include "util/string.h"
      26             : #include "util/numeric.h"
      27             : 
      28           2 : ChatBuffer::ChatBuffer(u32 scrollback):
      29             :         m_scrollback(scrollback),
      30             :         m_unformatted(),
      31             :         m_cols(0),
      32             :         m_rows(0),
      33             :         m_scroll(0),
      34             :         m_formatted(),
      35           2 :         m_empty_formatted_line()
      36             : {
      37           2 :         if (m_scrollback == 0)
      38           0 :                 m_scrollback = 1;
      39           2 :         m_empty_formatted_line.first = true;
      40           2 : }
      41             : 
      42           2 : ChatBuffer::~ChatBuffer()
      43             : {
      44           2 : }
      45             : 
      46           8 : void ChatBuffer::addLine(std::wstring name, std::wstring text)
      47             : {
      48          16 :         ChatLine line(name, text);
      49           8 :         m_unformatted.push_back(line);
      50             : 
      51           8 :         if (m_rows > 0)
      52             :         {
      53             :                 // m_formatted is valid and must be kept valid
      54           0 :                 bool scrolled_at_bottom = (m_scroll == getBottomScrollPos());
      55           0 :                 u32 num_added = formatChatLine(line, m_cols, m_formatted);
      56           0 :                 if (scrolled_at_bottom)
      57           0 :                         m_scroll += num_added;
      58             :         }
      59             : 
      60             :         // Limit number of lines by m_scrollback
      61           8 :         if (m_unformatted.size() > m_scrollback)
      62             :         {
      63           0 :                 deleteOldest(m_unformatted.size() - m_scrollback);
      64             :         }
      65           8 : }
      66             : 
      67           1 : void ChatBuffer::clear()
      68             : {
      69           1 :         m_unformatted.clear();
      70           1 :         m_formatted.clear();
      71           1 :         m_scroll = 0;
      72           1 : }
      73             : 
      74        5830 : u32 ChatBuffer::getLineCount() const
      75             : {
      76        5830 :         return m_unformatted.size();
      77             : }
      78             : 
      79           0 : u32 ChatBuffer::getScrollback() const
      80             : {
      81           0 :         return m_scrollback;
      82             : }
      83             : 
      84        3498 : const ChatLine& ChatBuffer::getLine(u32 index) const
      85             : {
      86             :         assert(index < getLineCount());      // pre-condition
      87        3498 :         return m_unformatted[index];
      88             : }
      89             : 
      90        1166 : void ChatBuffer::step(f32 dtime)
      91             : {
      92        4664 :         for (u32 i = 0; i < m_unformatted.size(); ++i)
      93             :         {
      94        3498 :                 m_unformatted[i].age += dtime;
      95             :         }
      96        1166 : }
      97             : 
      98        1166 : void ChatBuffer::deleteOldest(u32 count)
      99             : {
     100        1166 :         u32 del_unformatted = 0;
     101        1166 :         u32 del_formatted = 0;
     102             : 
     103        1166 :         while (count > 0 && del_unformatted < m_unformatted.size())
     104             :         {
     105           0 :                 ++del_unformatted;
     106             : 
     107             :                 // keep m_formatted in sync
     108           0 :                 if (del_formatted < m_formatted.size())
     109             :                 {
     110             : 
     111           0 :                         sanity_check(m_formatted[del_formatted].first);
     112           0 :                         ++del_formatted;
     113           0 :                         while (del_formatted < m_formatted.size() &&
     114           0 :                                         !m_formatted[del_formatted].first)
     115           0 :                                 ++del_formatted;
     116             :                 }
     117             : 
     118           0 :                 --count;
     119             :         }
     120             : 
     121        1166 :         m_unformatted.erase(m_unformatted.begin(), m_unformatted.begin() + del_unformatted);
     122        1166 :         m_formatted.erase(m_formatted.begin(), m_formatted.begin() + del_formatted);
     123        1166 : }
     124             : 
     125        1166 : void ChatBuffer::deleteByAge(f32 maxAge)
     126             : {
     127        1166 :         u32 count = 0;
     128        1166 :         while (count < m_unformatted.size() && m_unformatted[count].age > maxAge)
     129           0 :                 ++count;
     130        1166 :         deleteOldest(count);
     131        1166 : }
     132             : 
     133           0 : u32 ChatBuffer::getColumns() const
     134             : {
     135           0 :         return m_cols;
     136             : }
     137             : 
     138           0 : u32 ChatBuffer::getRows() const
     139             : {
     140           0 :         return m_rows;
     141             : }
     142             : 
     143           1 : void ChatBuffer::reformat(u32 cols, u32 rows)
     144             : {
     145           1 :         if (cols == 0 || rows == 0)
     146             :         {
     147             :                 // Clear formatted buffer
     148           1 :                 m_cols = 0;
     149           1 :                 m_rows = 0;
     150           1 :                 m_scroll = 0;
     151           1 :                 m_formatted.clear();
     152             :         }
     153           0 :         else if (cols != m_cols || rows != m_rows)
     154             :         {
     155             :                 // TODO: Avoid reformatting ALL lines (even invisible ones)
     156             :                 // each time the console size changes.
     157             : 
     158             :                 // Find out the scroll position in *unformatted* lines
     159           0 :                 u32 restore_scroll_unformatted = 0;
     160           0 :                 u32 restore_scroll_formatted = 0;
     161           0 :                 bool at_bottom = (m_scroll == getBottomScrollPos());
     162           0 :                 if (!at_bottom)
     163             :                 {
     164           0 :                         for (s32 i = 0; i < m_scroll; ++i)
     165             :                         {
     166           0 :                                 if (m_formatted[i].first)
     167           0 :                                         ++restore_scroll_unformatted;
     168             :                         }
     169             :                 }
     170             : 
     171             :                 // If number of columns change, reformat everything
     172           0 :                 if (cols != m_cols)
     173             :                 {
     174           0 :                         m_formatted.clear();
     175           0 :                         for (u32 i = 0; i < m_unformatted.size(); ++i)
     176             :                         {
     177           0 :                                 if (i == restore_scroll_unformatted)
     178           0 :                                         restore_scroll_formatted = m_formatted.size();
     179           0 :                                 formatChatLine(m_unformatted[i], cols, m_formatted);
     180             :                         }
     181             :                 }
     182             : 
     183             :                 // Update the console size
     184           0 :                 m_cols = cols;
     185           0 :                 m_rows = rows;
     186             : 
     187             :                 // Restore the scroll position
     188           0 :                 if (at_bottom)
     189             :                 {
     190           0 :                         scrollBottom();
     191             :                 }
     192             :                 else
     193             :                 {
     194           0 :                         scrollAbsolute(restore_scroll_formatted);
     195             :                 }
     196             :         }
     197           1 : }
     198             : 
     199           0 : const ChatFormattedLine& ChatBuffer::getFormattedLine(u32 row) const
     200             : {
     201           0 :         s32 index = m_scroll + (s32) row;
     202           0 :         if (index >= 0 && index < (s32) m_formatted.size())
     203           0 :                 return m_formatted[index];
     204             :         else
     205           0 :                 return m_empty_formatted_line;
     206             : }
     207             : 
     208           0 : void ChatBuffer::scroll(s32 rows)
     209             : {
     210           0 :         scrollAbsolute(m_scroll + rows);
     211           0 : }
     212             : 
     213           0 : void ChatBuffer::scrollAbsolute(s32 scroll)
     214             : {
     215           0 :         s32 top = getTopScrollPos();
     216           0 :         s32 bottom = getBottomScrollPos();
     217             : 
     218           0 :         m_scroll = scroll;
     219           0 :         if (m_scroll < top)
     220           0 :                 m_scroll = top;
     221           0 :         if (m_scroll > bottom)
     222           0 :                 m_scroll = bottom;
     223           0 : }
     224             : 
     225           0 : void ChatBuffer::scrollBottom()
     226             : {
     227           0 :         m_scroll = getBottomScrollPos();
     228           0 : }
     229             : 
     230           0 : void ChatBuffer::scrollTop()
     231             : {
     232           0 :         m_scroll = getTopScrollPos();
     233           0 : }
     234             : 
     235           0 : u32 ChatBuffer::formatChatLine(const ChatLine& line, u32 cols,
     236             :                 std::vector<ChatFormattedLine>& destination) const
     237             : {
     238           0 :         u32 num_added = 0;
     239           0 :         std::vector<ChatFormattedFragment> next_frags;
     240           0 :         ChatFormattedLine next_line;
     241           0 :         ChatFormattedFragment temp_frag;
     242           0 :         u32 out_column = 0;
     243           0 :         u32 in_pos = 0;
     244           0 :         u32 hanging_indentation = 0;
     245             : 
     246             :         // Format the sender name and produce fragments
     247           0 :         if (!line.name.empty())
     248             :         {
     249           0 :                 temp_frag.text = L"<";
     250           0 :                 temp_frag.column = 0;
     251             :                 //temp_frag.bold = 0;
     252           0 :                 next_frags.push_back(temp_frag);
     253           0 :                 temp_frag.text = line.name;
     254           0 :                 temp_frag.column = 0;
     255             :                 //temp_frag.bold = 1;
     256           0 :                 next_frags.push_back(temp_frag);
     257           0 :                 temp_frag.text = L"> ";
     258           0 :                 temp_frag.column = 0;
     259             :                 //temp_frag.bold = 0;
     260           0 :                 next_frags.push_back(temp_frag);
     261             :         }
     262             : 
     263             :         // Choose an indentation level
     264           0 :         if (line.name.empty())
     265             :         {
     266             :                 // Server messages
     267           0 :                 hanging_indentation = 0;
     268             :         }
     269           0 :         else if (line.name.size() + 3 <= cols/2)
     270             :         {
     271             :                 // Names shorter than about half the console width
     272           0 :                 hanging_indentation = line.name.size() + 3;
     273             :         }
     274             :         else
     275             :         {
     276             :                 // Very long names
     277           0 :                 hanging_indentation = 2;
     278             :         }
     279             : 
     280           0 :         next_line.first = true;
     281           0 :         bool text_processing = false;
     282             : 
     283             :         // Produce fragments and layout them into lines
     284           0 :         while (!next_frags.empty() || in_pos < line.text.size())
     285             :         {
     286             :                 // Layout fragments into lines
     287           0 :                 while (!next_frags.empty())
     288             :                 {
     289           0 :                         ChatFormattedFragment& frag = next_frags[0];
     290           0 :                         if (frag.text.size() <= cols - out_column)
     291             :                         {
     292             :                                 // Fragment fits into current line
     293           0 :                                 frag.column = out_column;
     294           0 :                                 next_line.fragments.push_back(frag);
     295           0 :                                 out_column += frag.text.size();
     296           0 :                                 next_frags.erase(next_frags.begin());
     297             :                         }
     298             :                         else
     299             :                         {
     300             :                                 // Fragment does not fit into current line
     301             :                                 // So split it up
     302           0 :                                 temp_frag.text = frag.text.substr(0, cols - out_column);
     303           0 :                                 temp_frag.column = out_column;
     304             :                                 //temp_frag.bold = frag.bold;
     305           0 :                                 next_line.fragments.push_back(temp_frag);
     306           0 :                                 frag.text = frag.text.substr(cols - out_column);
     307           0 :                                 out_column = cols;
     308             :                         }
     309           0 :                         if (out_column == cols || text_processing)
     310             :                         {
     311             :                                 // End the current line
     312           0 :                                 destination.push_back(next_line);
     313           0 :                                 num_added++;
     314           0 :                                 next_line.fragments.clear();
     315           0 :                                 next_line.first = false;
     316             : 
     317           0 :                                 out_column = text_processing ? hanging_indentation : 0;
     318             :                         }
     319             :                 }
     320             : 
     321             :                 // Produce fragment
     322           0 :                 if (in_pos < line.text.size())
     323             :                 {
     324           0 :                         u32 remaining_in_input = line.text.size() - in_pos;
     325           0 :                         u32 remaining_in_output = cols - out_column;
     326             : 
     327             :                         // Determine a fragment length <= the minimum of
     328             :                         // remaining_in_{in,out}put. Try to end the fragment
     329             :                         // on a word boundary.
     330           0 :                         u32 frag_length = 1, space_pos = 0;
     331           0 :                         while (frag_length < remaining_in_input &&
     332             :                                         frag_length < remaining_in_output)
     333             :                         {
     334           0 :                                 if (isspace(line.text[in_pos + frag_length]))
     335           0 :                                         space_pos = frag_length;
     336           0 :                                 ++frag_length;
     337             :                         }
     338           0 :                         if (space_pos != 0 && frag_length < remaining_in_input)
     339           0 :                                 frag_length = space_pos + 1;
     340             : 
     341           0 :                         temp_frag.text = line.text.substr(in_pos, frag_length);
     342           0 :                         temp_frag.column = 0;
     343             :                         //temp_frag.bold = 0;
     344           0 :                         next_frags.push_back(temp_frag);
     345           0 :                         in_pos += frag_length;
     346           0 :                         text_processing = true;
     347             :                 }
     348             :         }
     349             : 
     350             :         // End the last line
     351           0 :         if (num_added == 0 || !next_line.fragments.empty())
     352             :         {
     353           0 :                 destination.push_back(next_line);
     354           0 :                 num_added++;
     355             :         }
     356             : 
     357           0 :         return num_added;
     358             : }
     359             : 
     360           0 : s32 ChatBuffer::getTopScrollPos() const
     361             : {
     362           0 :         s32 formatted_count = (s32) m_formatted.size();
     363           0 :         s32 rows = (s32) m_rows;
     364           0 :         if (rows == 0)
     365           0 :                 return 0;
     366           0 :         else if (formatted_count <= rows)
     367           0 :                 return formatted_count - rows;
     368             :         else
     369           0 :                 return 0;
     370             : }
     371             : 
     372           0 : s32 ChatBuffer::getBottomScrollPos() const
     373             : {
     374           0 :         s32 formatted_count = (s32) m_formatted.size();
     375           0 :         s32 rows = (s32) m_rows;
     376           0 :         if (rows == 0)
     377           0 :                 return 0;
     378             :         else
     379           0 :                 return formatted_count - rows;
     380             : }
     381             : 
     382             : 
     383             : 
     384           1 : ChatPrompt::ChatPrompt(std::wstring prompt, u32 history_limit):
     385             :         m_prompt(prompt),
     386             :         m_line(L""),
     387             :         m_history(),
     388             :         m_history_index(0),
     389             :         m_history_limit(history_limit),
     390             :         m_cols(0),
     391             :         m_view(0),
     392             :         m_cursor(0),
     393             :         m_nick_completion_start(0),
     394           1 :         m_nick_completion_end(0)
     395             : {
     396           1 : }
     397             : 
     398           1 : ChatPrompt::~ChatPrompt()
     399             : {
     400           1 : }
     401             : 
     402           0 : void ChatPrompt::input(wchar_t ch)
     403             : {
     404           0 :         m_line.insert(m_cursor, 1, ch);
     405           0 :         m_cursor++;
     406           0 :         clampView();
     407           0 :         m_nick_completion_start = 0;
     408           0 :         m_nick_completion_end = 0;
     409           0 : }
     410             : 
     411           0 : void ChatPrompt::input(const std::wstring &str)
     412             : {
     413           0 :         m_line.insert(m_cursor, str);
     414           0 :         m_cursor += str.size();
     415           0 :         clampView();
     416           0 :         m_nick_completion_start = 0;
     417           0 :         m_nick_completion_end = 0;
     418           0 : }
     419             : 
     420           0 : std::wstring ChatPrompt::submit()
     421             : {
     422           0 :         std::wstring line = m_line;
     423           0 :         m_line.clear();
     424           0 :         if (!line.empty())
     425           0 :                 m_history.push_back(line);
     426           0 :         if (m_history.size() > m_history_limit)
     427           0 :                 m_history.erase(m_history.begin());
     428           0 :         m_history_index = m_history.size();
     429           0 :         m_view = 0;
     430           0 :         m_cursor = 0;
     431           0 :         m_nick_completion_start = 0;
     432           0 :         m_nick_completion_end = 0;
     433           0 :         return line;
     434             : }
     435             : 
     436           0 : void ChatPrompt::clear()
     437             : {
     438           0 :         m_line.clear();
     439           0 :         m_view = 0;
     440           0 :         m_cursor = 0;
     441           0 :         m_nick_completion_start = 0;
     442           0 :         m_nick_completion_end = 0;
     443           0 : }
     444             : 
     445           0 : void ChatPrompt::replace(std::wstring line)
     446             : {
     447           0 :         m_line =  line;
     448           0 :         m_view = m_cursor = line.size();
     449           0 :         clampView();
     450           0 :         m_nick_completion_start = 0;
     451           0 :         m_nick_completion_end = 0;
     452           0 : }
     453             : 
     454           0 : void ChatPrompt::historyPrev()
     455             : {
     456           0 :         if (m_history_index != 0)
     457             :         {
     458           0 :                 --m_history_index;
     459           0 :                 replace(m_history[m_history_index]);
     460             :         }
     461           0 : }
     462             : 
     463           0 : void ChatPrompt::historyNext()
     464             : {
     465           0 :         if (m_history_index + 1 >= m_history.size())
     466             :         {
     467           0 :                 m_history_index = m_history.size();
     468           0 :                 replace(L"");
     469             :         }
     470             :         else
     471             :         {
     472           0 :                 ++m_history_index;
     473           0 :                 replace(m_history[m_history_index]);
     474             :         }
     475           0 : }
     476             : 
     477           0 : void ChatPrompt::nickCompletion(const std::list<std::string>& names, bool backwards)
     478             : {
     479             :         // Two cases:
     480             :         // (a) m_nick_completion_start == m_nick_completion_end == 0
     481             :         //     Then no previous nick completion is active.
     482             :         //     Get the word around the cursor and replace with any nick
     483             :         //     that has that word as a prefix.
     484             :         // (b) else, continue a previous nick completion.
     485             :         //     m_nick_completion_start..m_nick_completion_end are the
     486             :         //     interval where the originally used prefix was. Cycle
     487             :         //     through the list of completions of that prefix.
     488           0 :         u32 prefix_start = m_nick_completion_start;
     489           0 :         u32 prefix_end = m_nick_completion_end;
     490           0 :         bool initial = (prefix_end == 0);
     491           0 :         if (initial)
     492             :         {
     493             :                 // no previous nick completion is active
     494           0 :                 prefix_start = prefix_end = m_cursor;
     495           0 :                 while (prefix_start > 0 && !isspace(m_line[prefix_start-1]))
     496           0 :                         --prefix_start;
     497           0 :                 while (prefix_end < m_line.size() && !isspace(m_line[prefix_end]))
     498           0 :                         ++prefix_end;
     499           0 :                 if (prefix_start == prefix_end)
     500           0 :                         return;
     501             :         }
     502           0 :         std::wstring prefix = m_line.substr(prefix_start, prefix_end - prefix_start);
     503             : 
     504             :         // find all names that start with the selected prefix
     505           0 :         std::vector<std::wstring> completions;
     506           0 :         for (std::list<std::string>::const_iterator
     507           0 :                         i = names.begin();
     508           0 :                         i != names.end(); ++i)
     509             :         {
     510           0 :                 if (str_starts_with(narrow_to_wide(*i), prefix, true))
     511             :                 {
     512           0 :                         std::wstring completion = narrow_to_wide(*i);
     513           0 :                         if (prefix_start == 0)
     514           0 :                                 completion += L": ";
     515           0 :                         completions.push_back(completion);
     516             :                 }
     517             :         }
     518           0 :         if (completions.empty())
     519           0 :                 return;
     520             : 
     521             :         // find a replacement string and the word that will be replaced
     522           0 :         u32 word_end = prefix_end;
     523           0 :         u32 replacement_index = 0;
     524           0 :         if (!initial)
     525             :         {
     526           0 :                 while (word_end < m_line.size() && !isspace(m_line[word_end]))
     527           0 :                         ++word_end;
     528           0 :                 std::wstring word = m_line.substr(prefix_start, word_end - prefix_start);
     529             : 
     530             :                 // cycle through completions
     531           0 :                 for (u32 i = 0; i < completions.size(); ++i)
     532             :                 {
     533           0 :                         if (str_equal(word, completions[i], true))
     534             :                         {
     535           0 :                                 if (backwards)
     536           0 :                                         replacement_index = i + completions.size() - 1;
     537             :                                 else
     538           0 :                                         replacement_index = i + 1;
     539           0 :                                 replacement_index %= completions.size();
     540           0 :                                 break;
     541             :                         }
     542             :                 }
     543             :         }
     544           0 :         std::wstring replacement = completions[replacement_index];
     545           0 :         if (word_end < m_line.size() && isspace(word_end))
     546           0 :                 ++word_end;
     547             : 
     548             :         // replace existing word with replacement word,
     549             :         // place the cursor at the end and record the completion prefix
     550           0 :         m_line.replace(prefix_start, word_end - prefix_start, replacement);
     551           0 :         m_cursor = prefix_start + replacement.size();
     552           0 :         clampView();
     553           0 :         m_nick_completion_start = prefix_start;
     554           0 :         m_nick_completion_end = prefix_end;
     555             : }
     556             : 
     557           1 : void ChatPrompt::reformat(u32 cols)
     558             : {
     559           1 :         if (cols <= m_prompt.size())
     560             :         {
     561           1 :                 m_cols = 0;
     562           1 :                 m_view = m_cursor;
     563             :         }
     564             :         else
     565             :         {
     566           0 :                 s32 length = m_line.size();
     567           0 :                 bool was_at_end = (m_view + m_cols >= length + 1);
     568           0 :                 m_cols = cols - m_prompt.size();
     569           0 :                 if (was_at_end)
     570           0 :                         m_view = length;
     571           0 :                 clampView();
     572             :         }
     573           1 : }
     574             : 
     575           0 : std::wstring ChatPrompt::getVisiblePortion() const
     576             : {
     577           0 :         return m_prompt + m_line.substr(m_view, m_cols);
     578             : }
     579             : 
     580           0 : s32 ChatPrompt::getVisibleCursorPosition() const
     581             : {
     582           0 :         return m_cursor - m_view + m_prompt.size();
     583             : }
     584             : 
     585           0 : void ChatPrompt::cursorOperation(CursorOp op, CursorOpDir dir, CursorOpScope scope)
     586             : {
     587           0 :         s32 old_cursor = m_cursor;
     588           0 :         s32 new_cursor = m_cursor;
     589             : 
     590           0 :         s32 length = m_line.size();
     591           0 :         s32 increment = (dir == CURSOROP_DIR_RIGHT) ? 1 : -1;
     592             : 
     593           0 :         if (scope == CURSOROP_SCOPE_CHARACTER)
     594             :         {
     595           0 :                 new_cursor += increment;
     596             :         }
     597           0 :         else if (scope == CURSOROP_SCOPE_WORD)
     598             :         {
     599           0 :                 if (increment > 0)
     600             :                 {
     601             :                         // skip one word to the right
     602           0 :                         while (new_cursor < length && isspace(m_line[new_cursor]))
     603           0 :                                 new_cursor++;
     604           0 :                         while (new_cursor < length && !isspace(m_line[new_cursor]))
     605           0 :                                 new_cursor++;
     606           0 :                         while (new_cursor < length && isspace(m_line[new_cursor]))
     607           0 :                                 new_cursor++;
     608             :                 }
     609             :                 else
     610             :                 {
     611             :                         // skip one word to the left
     612           0 :                         while (new_cursor >= 1 && isspace(m_line[new_cursor - 1]))
     613           0 :                                 new_cursor--;
     614           0 :                         while (new_cursor >= 1 && !isspace(m_line[new_cursor - 1]))
     615           0 :                                 new_cursor--;
     616             :                 }
     617             :         }
     618           0 :         else if (scope == CURSOROP_SCOPE_LINE)
     619             :         {
     620           0 :                 new_cursor += increment * length;
     621             :         }
     622             : 
     623           0 :         new_cursor = MYMAX(MYMIN(new_cursor, length), 0);
     624             : 
     625           0 :         if (op == CURSOROP_MOVE)
     626             :         {
     627           0 :                 m_cursor = new_cursor;
     628             :         }
     629           0 :         else if (op == CURSOROP_DELETE)
     630             :         {
     631           0 :                 if (new_cursor < old_cursor)
     632             :                 {
     633           0 :                         m_line.erase(new_cursor, old_cursor - new_cursor);
     634           0 :                         m_cursor = new_cursor;
     635             :                 }
     636           0 :                 else if (new_cursor > old_cursor)
     637             :                 {
     638           0 :                         m_line.erase(old_cursor, new_cursor - old_cursor);
     639           0 :                         m_cursor = old_cursor;
     640             :                 }
     641             :         }
     642             : 
     643           0 :         clampView();
     644             : 
     645           0 :         m_nick_completion_start = 0;
     646           0 :         m_nick_completion_end = 0;
     647           0 : }
     648             : 
     649           0 : void ChatPrompt::clampView()
     650             : {
     651           0 :         s32 length = m_line.size();
     652           0 :         if (length + 1 <= m_cols)
     653             :         {
     654           0 :                 m_view = 0;
     655             :         }
     656             :         else
     657             :         {
     658           0 :                 m_view = MYMIN(m_view, length + 1 - m_cols);
     659           0 :                 m_view = MYMIN(m_view, m_cursor);
     660           0 :                 m_view = MYMAX(m_view, m_cursor - m_cols + 1);
     661           0 :                 m_view = MYMAX(m_view, 0);
     662             :         }
     663           0 : }
     664             : 
     665             : 
     666             : 
     667           1 : ChatBackend::ChatBackend():
     668             :         m_console_buffer(500),
     669             :         m_recent_buffer(6),
     670           1 :         m_prompt(L"]", 500)
     671             : {
     672           1 : }
     673             : 
     674           1 : ChatBackend::~ChatBackend()
     675             : {
     676           1 : }
     677             : 
     678           4 : void ChatBackend::addMessage(std::wstring name, std::wstring text)
     679             : {
     680             :         // Note: A message may consist of multiple lines, for example the MOTD.
     681           8 :         WStrfnd fnd(text);
     682          12 :         while (!fnd.atend())
     683             :         {
     684           8 :                 std::wstring line = fnd.next(L"\n");
     685           4 :                 m_console_buffer.addLine(name, line);
     686           4 :                 m_recent_buffer.addLine(name, line);
     687             :         }
     688           4 : }
     689             : 
     690           2 : void ChatBackend::addUnparsedMessage(std::wstring message)
     691             : {
     692             :         // TODO: Remove the need to parse chat messages client-side, by sending
     693             :         // separate name and text fields in TOCLIENT_CHAT_MESSAGE.
     694             : 
     695           2 :         if (message.size() >= 2 && message[0] == L'<')
     696             :         {
     697           0 :                 std::size_t closing = message.find_first_of(L'>', 1);
     698           0 :                 if (closing != std::wstring::npos &&
     699           0 :                                 closing + 2 <= message.size() &&
     700           0 :                                 message[closing+1] == L' ')
     701             :                 {
     702           0 :                         std::wstring name = message.substr(1, closing - 1);
     703           0 :                         std::wstring text = message.substr(closing + 2);
     704           0 :                         addMessage(name, text);
     705           0 :                         return;
     706             :                 }
     707             :         }
     708             : 
     709             :         // Unable to parse, probably a server message.
     710           2 :         addMessage(L"", message);
     711             : }
     712             : 
     713           0 : ChatBuffer& ChatBackend::getConsoleBuffer()
     714             : {
     715           0 :         return m_console_buffer;
     716             : }
     717             : 
     718        1166 : ChatBuffer& ChatBackend::getRecentBuffer()
     719             : {
     720        1166 :         return m_recent_buffer;
     721             : }
     722             : 
     723        1166 : std::wstring ChatBackend::getRecentChat()
     724             : {
     725        2332 :         std::wostringstream stream;
     726        4664 :         for (u32 i = 0; i < m_recent_buffer.getLineCount(); ++i)
     727             :         {
     728        3498 :                 const ChatLine& line = m_recent_buffer.getLine(i);
     729        3498 :                 if (i != 0)
     730        2332 :                         stream << L"\n";
     731        3498 :                 if (!line.name.empty())
     732           0 :                         stream << L"<" << line.name << L"> ";
     733        3498 :                 stream << line.text;
     734             :         }
     735        2332 :         return stream.str();
     736             : }
     737             : 
     738           0 : ChatPrompt& ChatBackend::getPrompt()
     739             : {
     740           0 :         return m_prompt;
     741             : }
     742             : 
     743           1 : void ChatBackend::reformat(u32 cols, u32 rows)
     744             : {
     745           1 :         m_console_buffer.reformat(cols, rows);
     746             : 
     747             :         // no need to reformat m_recent_buffer, its formatted lines
     748             :         // are not used
     749             : 
     750           1 :         m_prompt.reformat(cols);
     751           1 : }
     752             : 
     753           1 : void ChatBackend::clearRecentChat()
     754             : {
     755           1 :         m_recent_buffer.clear();
     756           1 : }
     757             : 
     758        1166 : void ChatBackend::step(float dtime)
     759             : {
     760        1166 :         m_recent_buffer.step(dtime);
     761        1166 :         m_recent_buffer.deleteByAge(60.0);
     762             : 
     763             :         // no need to age messages in anything but m_recent_buffer
     764        1166 : }
     765             : 
     766           0 : void ChatBackend::scroll(s32 rows)
     767             : {
     768           0 :         m_console_buffer.scroll(rows);
     769           0 : }
     770             : 
     771           0 : void ChatBackend::scrollPageDown()
     772             : {
     773           0 :         m_console_buffer.scroll(m_console_buffer.getRows());
     774           0 : }
     775             : 
     776           0 : void ChatBackend::scrollPageUp()
     777             : {
     778           0 :         m_console_buffer.scroll(-(s32)m_console_buffer.getRows());
     779           3 : }

Generated by: LCOV version 1.11