mirror of
https://github.com/luanti-org/luanti.git
synced 2025-06-27 16:36:03 +00:00
Make chat web links clickable (#11092)
If enabled in minetest.conf, provides colored, clickable (middle-mouse or ctrl-left-mouse) weblinks in chat output, to open the OS' default web browser.
This commit is contained in:
parent
e1b297a14b
commit
1805775f3d
8 changed files with 242 additions and 30 deletions
133
src/chat.cpp
133
src/chat.cpp
|
@ -35,6 +35,17 @@ ChatBuffer::ChatBuffer(u32 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)
|
||||
|
@ -263,78 +274,144 @@ u32 ChatBuffer::formatChatLine(const ChatLine& line, u32 cols,
|
|||
//EnrichedString line_text(line.text);
|
||||
|
||||
next_line.first = true;
|
||||
bool text_processing = false;
|
||||
// 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())
|
||||
{
|
||||
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())
|
||||
{
|
||||
while (!next_frags.empty()) {
|
||||
ChatFormattedFragment& frag = next_frags[0];
|
||||
if (frag.text.size() <= cols - out_column)
|
||||
{
|
||||
|
||||
// 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
|
||||
{
|
||||
} 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.bold = frag.bold;
|
||||
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 || text_processing)
|
||||
{
|
||||
|
||||
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 = text_processing ? hanging_indentation : 0;
|
||||
out_column = hanging_indentation;
|
||||
mark_newline = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Produce fragment
|
||||
if (in_pos < line.text.size())
|
||||
{
|
||||
u32 remaining_in_input = line.text.size() - in_pos;
|
||||
u32 remaining_in_output = cols - out_column;
|
||||
// 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 = 1, space_pos = 0;
|
||||
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(line.text.getString()[in_pos + frag_length]))
|
||||
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);
|
||||
temp_frag.column = 0;
|
||||
//temp_frag.bold = 0;
|
||||
// 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;
|
||||
text_processing = true;
|
||||
remaining_in_output -= std::min(frag_length, remaining_in_output);
|
||||
}
|
||||
}
|
||||
|
||||
// End the last line
|
||||
if (num_added == 0 || !next_line.fragments.empty())
|
||||
{
|
||||
if (num_added == 0 || !next_line.fragments.empty()) {
|
||||
destination.push_back(next_line);
|
||||
num_added++;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue