mirror of
				https://github.com/luanti-org/luanti.git
				synced 2025-11-04 09:15:29 +01:00 
			
		
		
		
	Improve chat history (#12975)
This commit is contained in:
		
				
					committed by
					
						
						GitHub
					
				
			
			
				
	
			
			
			
						parent
						
							8fded9d990
						
					
				
				
					commit
					2f9f0c0900
				
			
							
								
								
									
										122
									
								
								src/chat.cpp
									
									
									
									
									
								
							
							
						
						
									
										122
									
								
								src/chat.cpp
									
									
									
									
									
								
							@@ -454,9 +454,36 @@ ChatPrompt::ChatPrompt(const std::wstring &prompt, u32 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)
 | 
			
		||||
{
 | 
			
		||||
	m_line.insert(m_cursor, 1, ch);
 | 
			
		||||
	makeLineRef().insert(m_cursor, 1, ch);
 | 
			
		||||
	m_cursor++;
 | 
			
		||||
	clampView();
 | 
			
		||||
	m_nick_completion_start = 0;
 | 
			
		||||
@@ -465,7 +492,7 @@ void ChatPrompt::input(wchar_t ch)
 | 
			
		||||
 | 
			
		||||
void ChatPrompt::input(const std::wstring &str)
 | 
			
		||||
{
 | 
			
		||||
	m_line.insert(m_cursor, str);
 | 
			
		||||
	makeLineRef().insert(m_cursor, str);
 | 
			
		||||
	m_cursor += str.size();
 | 
			
		||||
	clampView();
 | 
			
		||||
	m_nick_completion_start = 0;
 | 
			
		||||
@@ -474,22 +501,38 @@ void ChatPrompt::input(const std::wstring &str)
 | 
			
		||||
 | 
			
		||||
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)) {
 | 
			
		||||
			(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(),
 | 
			
		||||
			line), m_history.end());
 | 
			
		||||
		m_history.erase(std::remove(m_history.begin(), m_history.end(), entry),
 | 
			
		||||
				m_history.end());
 | 
			
		||||
		// Push unique line
 | 
			
		||||
		m_history.push_back(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()
 | 
			
		||||
{
 | 
			
		||||
	m_line.clear();
 | 
			
		||||
	makeLineRef().clear();
 | 
			
		||||
	m_view = 0;
 | 
			
		||||
	m_cursor = 0;
 | 
			
		||||
	m_nick_completion_start = 0;
 | 
			
		||||
@@ -498,8 +541,8 @@ void ChatPrompt::clear()
 | 
			
		||||
 | 
			
		||||
std::wstring ChatPrompt::replace(const std::wstring &line)
 | 
			
		||||
{
 | 
			
		||||
	std::wstring old_line = m_line;
 | 
			
		||||
	m_line =  line;
 | 
			
		||||
	std::wstring old_line = getLine();
 | 
			
		||||
	makeLineRef() = line;
 | 
			
		||||
	m_view = m_cursor = line.size();
 | 
			
		||||
	clampView();
 | 
			
		||||
	m_nick_completion_start = 0;
 | 
			
		||||
@@ -509,24 +552,23 @@ std::wstring ChatPrompt::replace(const std::wstring &line)
 | 
			
		||||
 | 
			
		||||
void ChatPrompt::historyPrev()
 | 
			
		||||
{
 | 
			
		||||
	if (m_history_index != 0)
 | 
			
		||||
	{
 | 
			
		||||
	if (m_history_index != 0) {
 | 
			
		||||
		--m_history_index;
 | 
			
		||||
		replace(m_history[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 + 1 >= m_history.size())
 | 
			
		||||
	{
 | 
			
		||||
		m_history_index = m_history.size();
 | 
			
		||||
		replace(L"");
 | 
			
		||||
	}
 | 
			
		||||
	else
 | 
			
		||||
	{
 | 
			
		||||
		++m_history_index;
 | 
			
		||||
		replace(m_history[m_history_index]);
 | 
			
		||||
	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;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -541,6 +583,7 @@ void ChatPrompt::nickCompletion(const std::list<std::string>& names, bool backwa
 | 
			
		||||
	//     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);
 | 
			
		||||
@@ -548,14 +591,14 @@ void ChatPrompt::nickCompletion(const std::list<std::string>& names, bool backwa
 | 
			
		||||
	{
 | 
			
		||||
		// no previous nick completion is active
 | 
			
		||||
		prefix_start = prefix_end = m_cursor;
 | 
			
		||||
		while (prefix_start > 0 && !iswspace(m_line[prefix_start-1]))
 | 
			
		||||
		while (prefix_start > 0 && !iswspace(line[prefix_start-1]))
 | 
			
		||||
			--prefix_start;
 | 
			
		||||
		while (prefix_end < m_line.size() && !iswspace(m_line[prefix_end]))
 | 
			
		||||
		while (prefix_end < line.size() && !iswspace(line[prefix_end]))
 | 
			
		||||
			++prefix_end;
 | 
			
		||||
		if (prefix_start == prefix_end)
 | 
			
		||||
			return;
 | 
			
		||||
	}
 | 
			
		||||
	std::wstring prefix = m_line.substr(prefix_start, prefix_end - prefix_start);
 | 
			
		||||
	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;
 | 
			
		||||
@@ -576,9 +619,9 @@ void ChatPrompt::nickCompletion(const std::list<std::string>& names, bool backwa
 | 
			
		||||
	u32 replacement_index = 0;
 | 
			
		||||
	if (!initial)
 | 
			
		||||
	{
 | 
			
		||||
		while (word_end < m_line.size() && !iswspace(m_line[word_end]))
 | 
			
		||||
		while (word_end < line.size() && !iswspace(line[word_end]))
 | 
			
		||||
			++word_end;
 | 
			
		||||
		std::wstring word = m_line.substr(prefix_start, word_end - prefix_start);
 | 
			
		||||
		std::wstring word = line.substr(prefix_start, word_end - prefix_start);
 | 
			
		||||
 | 
			
		||||
		// cycle through completions
 | 
			
		||||
		for (u32 i = 0; i < completions.size(); ++i)
 | 
			
		||||
@@ -595,12 +638,12 @@ void ChatPrompt::nickCompletion(const std::list<std::string>& names, bool backwa
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	std::wstring replacement = completions[replacement_index];
 | 
			
		||||
	if (word_end < m_line.size() && iswspace(m_line[word_end]))
 | 
			
		||||
	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
 | 
			
		||||
	m_line.replace(prefix_start, word_end - prefix_start, replacement);
 | 
			
		||||
	makeLineRef().replace(prefix_start, word_end - prefix_start, replacement);
 | 
			
		||||
	m_cursor = prefix_start + replacement.size();
 | 
			
		||||
	clampView();
 | 
			
		||||
	m_nick_completion_start = prefix_start;
 | 
			
		||||
@@ -616,7 +659,7 @@ void ChatPrompt::reformat(u32 cols)
 | 
			
		||||
	}
 | 
			
		||||
	else
 | 
			
		||||
	{
 | 
			
		||||
		s32 length = m_line.size();
 | 
			
		||||
		s32 length = getLineRef().size();
 | 
			
		||||
		bool was_at_end = (m_view + m_cols >= length + 1);
 | 
			
		||||
		m_cols = cols - m_prompt.size();
 | 
			
		||||
		if (was_at_end)
 | 
			
		||||
@@ -627,7 +670,7 @@ void ChatPrompt::reformat(u32 cols)
 | 
			
		||||
 | 
			
		||||
std::wstring ChatPrompt::getVisiblePortion() const
 | 
			
		||||
{
 | 
			
		||||
	return m_prompt + m_line.substr(m_view, m_cols);
 | 
			
		||||
	return m_prompt + getLineRef().substr(m_view, m_cols);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
s32 ChatPrompt::getVisibleCursorPosition() const
 | 
			
		||||
@@ -640,7 +683,8 @@ void ChatPrompt::cursorOperation(CursorOp op, CursorOpDir dir, CursorOpScope sco
 | 
			
		||||
	s32 old_cursor = m_cursor;
 | 
			
		||||
	s32 new_cursor = m_cursor;
 | 
			
		||||
 | 
			
		||||
	s32 length = m_line.size();
 | 
			
		||||
	const std::wstring &line = getLineRef();
 | 
			
		||||
	s32 length = line.size();
 | 
			
		||||
	s32 increment = (dir == CURSOROP_DIR_RIGHT) ? 1 : -1;
 | 
			
		||||
 | 
			
		||||
	switch (scope) {
 | 
			
		||||
@@ -650,17 +694,17 @@ void ChatPrompt::cursorOperation(CursorOp op, CursorOpDir dir, CursorOpScope sco
 | 
			
		||||
	case CURSOROP_SCOPE_WORD:
 | 
			
		||||
		if (dir == CURSOROP_DIR_RIGHT) {
 | 
			
		||||
			// skip one word to the right
 | 
			
		||||
			while (new_cursor < length && iswspace(m_line[new_cursor]))
 | 
			
		||||
			while (new_cursor < length && iswspace(line[new_cursor]))
 | 
			
		||||
				new_cursor++;
 | 
			
		||||
			while (new_cursor < length && !iswspace(m_line[new_cursor]))
 | 
			
		||||
			while (new_cursor < length && !iswspace(line[new_cursor]))
 | 
			
		||||
				new_cursor++;
 | 
			
		||||
			while (new_cursor < length && iswspace(m_line[new_cursor]))
 | 
			
		||||
			while (new_cursor < length && iswspace(line[new_cursor]))
 | 
			
		||||
				new_cursor++;
 | 
			
		||||
		} else {
 | 
			
		||||
			// skip one word to the left
 | 
			
		||||
			while (new_cursor >= 1 && iswspace(m_line[new_cursor - 1]))
 | 
			
		||||
			while (new_cursor >= 1 && iswspace(line[new_cursor - 1]))
 | 
			
		||||
				new_cursor--;
 | 
			
		||||
			while (new_cursor >= 1 && !iswspace(m_line[new_cursor - 1]))
 | 
			
		||||
			while (new_cursor >= 1 && !iswspace(line[new_cursor - 1]))
 | 
			
		||||
				new_cursor--;
 | 
			
		||||
		}
 | 
			
		||||
		break;
 | 
			
		||||
@@ -680,10 +724,10 @@ void ChatPrompt::cursorOperation(CursorOp op, CursorOpDir dir, CursorOpScope sco
 | 
			
		||||
		break;
 | 
			
		||||
	case CURSOROP_DELETE:
 | 
			
		||||
		if (m_cursor_len > 0) { // Delete selected text first
 | 
			
		||||
			m_line.erase(m_cursor, m_cursor_len);
 | 
			
		||||
			makeLineRef().erase(m_cursor, m_cursor_len);
 | 
			
		||||
		} else {
 | 
			
		||||
			m_cursor = MYMIN(new_cursor, old_cursor);
 | 
			
		||||
			m_line.erase(m_cursor, abs(new_cursor - old_cursor));
 | 
			
		||||
			makeLineRef().erase(m_cursor, abs(new_cursor - old_cursor));
 | 
			
		||||
		}
 | 
			
		||||
		m_cursor_len = 0;
 | 
			
		||||
		break;
 | 
			
		||||
@@ -707,7 +751,7 @@ void ChatPrompt::cursorOperation(CursorOp op, CursorOpDir dir, CursorOpScope sco
 | 
			
		||||
 | 
			
		||||
void ChatPrompt::clampView()
 | 
			
		||||
{
 | 
			
		||||
	s32 length = m_line.size();
 | 
			
		||||
	s32 length = getLineRef().size();
 | 
			
		||||
	if (length + 1 <= m_cols)
 | 
			
		||||
	{
 | 
			
		||||
		m_view = 0;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										24
									
								
								src/chat.h
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								src/chat.h
									
									
									
									
									
								
							@@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 | 
			
		||||
 | 
			
		||||
#include "irrlichttypes.h"
 | 
			
		||||
#include "util/enriched_string.h"
 | 
			
		||||
#include "util/Optional.h"
 | 
			
		||||
#include "settings.h"
 | 
			
		||||
 | 
			
		||||
// Chat console related classes
 | 
			
		||||
@@ -172,10 +173,10 @@ public:
 | 
			
		||||
	void addToHistory(const std::wstring &line);
 | 
			
		||||
 | 
			
		||||
	// Get current line
 | 
			
		||||
	std::wstring getLine() const { return m_line; }
 | 
			
		||||
	std::wstring getLine() const { return getLineRef(); }
 | 
			
		||||
 | 
			
		||||
	// Get section of line that is currently selected
 | 
			
		||||
	std::wstring getSelection() const { return m_line.substr(m_cursor, m_cursor_len); }
 | 
			
		||||
	std::wstring getSelection() const { return getLineRef().substr(m_cursor, m_cursor_len); }
 | 
			
		||||
 | 
			
		||||
	// Clear the current line
 | 
			
		||||
	void clear();
 | 
			
		||||
@@ -233,18 +234,33 @@ public:
 | 
			
		||||
	void cursorOperation(CursorOp op, CursorOpDir dir, CursorOpScope scope);
 | 
			
		||||
 | 
			
		||||
protected:
 | 
			
		||||
	const std::wstring &getLineRef() const;
 | 
			
		||||
 | 
			
		||||
	std::wstring &makeLineRef();
 | 
			
		||||
 | 
			
		||||
	// set m_view to ensure that 0 <= m_view <= m_cursor < m_view + m_cols
 | 
			
		||||
	// if line can be fully shown, set m_view to zero
 | 
			
		||||
	// else, also ensure m_view <= m_line.size() + 1 - m_cols
 | 
			
		||||
	void clampView();
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	struct HistoryEntry {
 | 
			
		||||
		std::wstring line;
 | 
			
		||||
		// If line is edited, saved holds the unedited version.
 | 
			
		||||
		Optional<std::wstring> saved;
 | 
			
		||||
 | 
			
		||||
		HistoryEntry(const std::wstring &line): line(line) {}
 | 
			
		||||
 | 
			
		||||
		bool operator==(const HistoryEntry &other);
 | 
			
		||||
		bool operator!=(const HistoryEntry &other) { return !(*this == other); }
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	// Prompt prefix
 | 
			
		||||
	std::wstring m_prompt = L"";
 | 
			
		||||
	// Currently edited line
 | 
			
		||||
	// Non-historical edited line
 | 
			
		||||
	std::wstring m_line = L"";
 | 
			
		||||
	// History buffer
 | 
			
		||||
	std::vector<std::wstring> m_history;
 | 
			
		||||
	std::vector<HistoryEntry> m_history;
 | 
			
		||||
	// History index (0 <= m_history_index <= m_history.size())
 | 
			
		||||
	u32 m_history_index = 0;
 | 
			
		||||
	// Maximum number of history entries
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user