code.gitea.io/gitea@v1.21.7/web_src/js/render/ansi.js (about) 1 import {AnsiUp} from 'ansi_up'; 2 3 const replacements = [ 4 [/\x1b\[\d+[A-H]/g, ''], // Move cursor, treat them as no-op 5 [/\x1b\[\d?[JK]/g, '\r'], // Erase display/line, treat them as a Carriage Return 6 ]; 7 8 // render ANSI to HTML 9 export function renderAnsi(line) { 10 // create a fresh ansi_up instance because otherwise previous renders can influence 11 // the output of future renders, because ansi_up is stateful and remembers things like 12 // unclosed opening tags for colors. 13 const ansi_up = new AnsiUp(); 14 ansi_up.use_classes = true; 15 16 if (line.endsWith('\r\n')) { 17 line = line.substring(0, line.length - 2); 18 } else if (line.endsWith('\n')) { 19 line = line.substring(0, line.length - 1); 20 } 21 22 if (line.includes('\x1b')) { 23 for (const [regex, replacement] of replacements) { 24 line = line.replace(regex, replacement); 25 } 26 } 27 28 if (!line.includes('\r')) { 29 return ansi_up.ansi_to_html(line); 30 } 31 32 // handle "\rReading...1%\rReading...5%\rReading...100%", 33 // convert it into a multiple-line string: "Reading...1%\nReading...5%\nReading...100%" 34 const lines = []; 35 for (const part of line.split('\r')) { 36 if (part === '') continue; 37 const partHtml = ansi_up.ansi_to_html(part); 38 if (partHtml !== '') { 39 lines.push(partHtml); 40 } 41 } 42 43 // the log message element is with "white-space: break-spaces;", so use "\n" to break lines 44 return lines.join('\n'); 45 }