@@ -35,6 +35,17 @@ ChatBuffer::ChatBuffer(u32 scrollback):
3535 if (m_scrollback == 0 )
3636 m_scrollback = 1 ;
3737 m_empty_formatted_line.first = true ;
38+
39+ m_cache_clickable_chat_weblinks = false ;
40+ // Curses mode cannot access g_settings here
41+ if (g_settings != nullptr ) {
42+ m_cache_clickable_chat_weblinks = g_settings->getBool (" clickable_chat_weblinks" );
43+ if (m_cache_clickable_chat_weblinks) {
44+ std::string colorval = g_settings->get (" chat_weblink_color" );
45+ parseColorString (colorval, m_cache_chat_weblink_color, false , 255 );
46+ m_cache_chat_weblink_color.setAlpha (255 );
47+ }
48+ }
3849}
3950
4051void ChatBuffer::addLine (const std::wstring &name, const std::wstring &text)
@@ -263,78 +274,144 @@ u32 ChatBuffer::formatChatLine(const ChatLine& line, u32 cols,
263274 // EnrichedString line_text(line.text);
264275
265276 next_line.first = true ;
266- bool text_processing = false ;
277+ // Set/use forced newline after the last frag in each line
278+ bool mark_newline = false ;
267279
268280 // Produce fragments and layout them into lines
269- while (!next_frags.empty () || in_pos < line.text .size ())
270- {
281+ while (!next_frags.empty () || in_pos < line.text .size ()) {
282+ mark_newline = false ; // now using this to USE line-end frag
283+
271284 // Layout fragments into lines
272- while (!next_frags.empty ())
273- {
285+ while (!next_frags.empty ()) {
274286 ChatFormattedFragment& frag = next_frags[0 ];
275- if (frag.text .size () <= cols - out_column)
276- {
287+
288+ // Force newline after this frag, if marked
289+ if (frag.column == INT_MAX)
290+ mark_newline = true ;
291+
292+ if (frag.text .size () <= cols - out_column) {
277293 // Fragment fits into current line
278294 frag.column = out_column;
279295 next_line.fragments .push_back (frag);
280296 out_column += frag.text .size ();
281297 next_frags.erase (next_frags.begin ());
282- }
283- else
284- {
298+ } else {
285299 // Fragment does not fit into current line
286300 // So split it up
287301 temp_frag.text = frag.text .substr (0 , cols - out_column);
288302 temp_frag.column = out_column;
289- // temp_frag.bold = frag.bold;
303+ temp_frag.weblink = frag.weblink ;
304+
290305 next_line.fragments .push_back (temp_frag);
291306 frag.text = frag.text .substr (cols - out_column);
307+ frag.column = 0 ;
292308 out_column = cols;
293309 }
294- if (out_column == cols || text_processing)
295- {
310+
311+ if (out_column == cols || mark_newline) {
296312 // End the current line
297313 destination.push_back (next_line);
298314 num_added++;
299315 next_line.fragments .clear ();
300316 next_line.first = false ;
301317
302- out_column = text_processing ? hanging_indentation : 0 ;
318+ out_column = hanging_indentation;
319+ mark_newline = false ;
303320 }
304321 }
305322
306- // Produce fragment
307- if (in_pos < line.text .size ())
308- {
309- u32 remaining_in_input = line.text .size () - in_pos;
310- u32 remaining_in_output = cols - out_column;
323+ // Produce fragment(s) for next formatted line
324+ if (!(in_pos < line.text .size ()))
325+ continue ;
311326
327+ const std::wstring &linestring = line.text .getString ();
328+ u32 remaining_in_output = cols - out_column;
329+ size_t http_pos = std::wstring::npos;
330+ mark_newline = false ; // now using this to SET line-end frag
331+
332+ // Construct all frags for next output line
333+ while (!mark_newline) {
312334 // Determine a fragment length <= the minimum of
313335 // remaining_in_{in,out}put. Try to end the fragment
314336 // on a word boundary.
315- u32 frag_length = 1 , space_pos = 0 ;
337+ u32 frag_length = 0 , space_pos = 0 ;
338+ u32 remaining_in_input = line.text .size () - in_pos;
339+
340+ if (m_cache_clickable_chat_weblinks) {
341+ // Note: unsigned(-1) on fail
342+ http_pos = linestring.find (L" https://" , in_pos);
343+ if (http_pos == std::wstring::npos)
344+ http_pos = linestring.find (L" http://" , in_pos);
345+ if (http_pos != std::wstring::npos)
346+ http_pos -= in_pos;
347+ }
348+
316349 while (frag_length < remaining_in_input &&
317- frag_length < remaining_in_output)
318- {
319- if (iswspace (line.text .getString ()[in_pos + frag_length]))
350+ frag_length < remaining_in_output) {
351+ if (iswspace (linestring[in_pos + frag_length]))
320352 space_pos = frag_length;
321353 ++frag_length;
322354 }
355+
356+ if (http_pos >= remaining_in_output) {
357+ // Http not in range, grab until space or EOL, halt as normal.
358+ // Note this works because (http_pos = npos) is unsigned(-1)
359+
360+ mark_newline = true ;
361+ } else if (http_pos == 0 ) {
362+ // At http, grab ALL until FIRST whitespace or end marker. loop.
363+ // If at end of string, next loop will be empty string to mark end of weblink.
364+
365+ frag_length = 6 ; // Frag is at least "http://"
366+
367+ // Chars to mark end of weblink
368+ // TODO? replace this with a safer (slower) regex whitelist?
369+ static const std::wstring delim_chars = L" \'\" );," ;
370+ wchar_t tempchar = linestring[in_pos+frag_length];
371+ while (frag_length < remaining_in_input &&
372+ !iswspace (tempchar) &&
373+ delim_chars.find (tempchar) == std::wstring::npos) {
374+ ++frag_length;
375+ tempchar = linestring[in_pos+frag_length];
376+ }
377+
378+ space_pos = frag_length - 1 ;
379+ // This frag may need to be force-split. That's ok, urls aren't "words"
380+ if (frag_length >= remaining_in_output) {
381+ mark_newline = true ;
382+ }
383+ } else {
384+ // Http in range, grab until http, loop
385+
386+ space_pos = http_pos - 1 ;
387+ frag_length = http_pos;
388+ }
389+
390+ // Include trailing space in current frag
323391 if (space_pos != 0 && frag_length < remaining_in_input)
324392 frag_length = space_pos + 1 ;
325393
326394 temp_frag.text = line.text .substr (in_pos, frag_length);
327- temp_frag.column = 0 ;
328- // temp_frag.bold = 0;
395+ // A hack so this frag remembers mark_newline for the layout phase
396+ temp_frag.column = mark_newline ? INT_MAX : 0 ;
397+
398+ if (http_pos == 0 ) {
399+ // Discard color stuff from the source frag
400+ temp_frag.text = EnrichedString (temp_frag.text .getString ());
401+ temp_frag.text .setDefaultColor (m_cache_chat_weblink_color);
402+ // Set weblink in the frag meta
403+ temp_frag.weblink = wide_to_utf8 (temp_frag.text .getString ());
404+ } else {
405+ temp_frag.weblink .clear ();
406+ }
329407 next_frags.push_back (temp_frag);
330408 in_pos += frag_length;
331- text_processing = true ;
409+ remaining_in_output -= std::min (frag_length, remaining_in_output) ;
332410 }
333411 }
334412
335413 // End the last line
336- if (num_added == 0 || !next_line.fragments .empty ())
337- {
414+ if (num_added == 0 || !next_line.fragments .empty ()) {
338415 destination.push_back (next_line);
339416 num_added++;
340417 }
0 commit comments