-- this is essentially -- https://github.com/jupyter/nbconvert/blob/main/nbconvert/filters/ansi.py -- converted to lua -- good list of ANSI escape sequences: -- https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797 local ANSI_COLORS = { "ansi-black", "ansi-red", "ansi-green", "ansi-yellow", "ansi-blue", "ansi-magenta", "ansi-cyan", "ansi-white", "ansi-black-intense", "ansi-red-intense", "ansi-green-intense", "ansi-yellow-intense", "ansi-blue-intense", "ansi-magenta-intense", "ansi-cyan-intense", "ansi-white-intense" } local function get_extended_color(numbers) local n = table.remove(numbers, 1) local r,g,b,idx if n == 2 and #numbers >=3 then -- 24bit RGB r = table.remove(numbers, 1) g = table.remove(numbers, 1) b = table.remove(numbers, 1) elseif n == 5 and #numbers >= 1 then -- 256 colors idx = table.remove(numbers, 1) if idx < 16 then -- 16 default terminal colors return idx elseif idx < 232 then -- 6x6x6 color cube, see http://stackoverflow.com/a/27165165/500098 r = (idx - 16) // 36 r = 55 + r * 40 if r < 0 then r = 0 end g = ((idx - 16) % 36) // 6 g = 55 + g * 40 if g < 0 then g = 0 end b = (idx - 16) % 6 b = 55 + b * 40 if b < 0 then b = 0 end elseif idx < 256 then -- grayscale, see http://stackoverflow.com/a/27165165/500098 r = (idx - 232) * 10 + 8 g = r b = r end end return {r, g, b} end --[=[ local re = require "re" local ANSI = re.compile [[ '\x1b%[' {.*?} {[@-~]} ]] --]=] local function LaTeXconverter(fg, bg, bold, underline, inverse) if not (fg or bg or bold or underline or inverse) then return "","" end local starttag = "" local endtag = "" if inverse then fg, bg = bg, fg end if type(fg) == "number" then starttag = starttag .. [[\textcolor{]] .. ANSI_COLORS[fg+1] .. "}{" endtag = "}" .. endtag elseif type(fg) == "table" then -- See http://tex.stackexchange.com/a/291102/13684 starttag = starttag .. [[\def\tcRGB{\textcolor[RGB]}\expandafter]] starttag = starttag .. string.format([[\tcRGB\expandafter{\detokenize{%d,%d,%d}}{]], fg[1], fg[2], fg[3]) endtag = "}" .. endtag elseif inverse then starttag = starttag .. [[\textcolor{ansi-default-inverse-fg}{]] endtag = "}" .. endtag end if type(bg) == "number" then starttag = starttag .. [[\setlength{\fboxsep}{0pt}]] starttag = starttag .. [[\colorbox{]] .. ANSI_COLORS[bg+1] .. "}{" endtag = [[\strut}]] .. endtag elseif type(bg) == "table" then -- See http://tex.stackexchange.com/a/291102/13684 starttag = starttag .. [[\setlength{\fboxsep}{0pt}]] starttag = starttag .. [[\def\cbRGB{\colorbox[RGB]}\expandafter]] starttag = starttag .. string.format([[\cbRGB\expandafter{\detokenize{%d,%d,%d}}{]], bg[1], bg[2], bg[3]) endtag = [[\strut}]] .. endtag elseif inverse then starttag = starttag .. [[\setlength{\fboxsep}{0pt}]] starttag = starttag .. [[\colorbox{ansi-default-inverse-bg}{]] endtag = [[\strut}]] .. endtag end if bold then starttag = starttag .. [[\textbf{]] endtag = "}" .. endtag end if underline then starttag = starttag .. [[\underline{]] endtag = "}" .. endtag end return starttag, endtag end local function HTMLconverter(fg, bg, bold, underline, inverse) if not (fg or bg or bold or underline or inverse) then return "","" end local classes = {} local styles = {} local type = type -- more efficient? local next = next if inverse then fg, bg = bg, fg end if type(fg) == "number" then table.insert(classes, ANSI_COLORS[fg+1] .. "-fg") elseif type(fg) == "table" then table.insert(styles, string.format("color: rgb(%d,%d,%d)", fg[1], fg[2], fg[3])) elseif inverse then table.insert(classes, "ansi-default-inverse-fg") end if type(bg) == "number" then table.insert(classes, ANSI_COLORS[bg+1] .. "-bg") elseif type(bg) == "table" then table.insert(styles, string.format("background-color: rgb(%d,%d,%d)", bg[1], bg[2], bg[3])) elseif inverse then table.insert(classes, "ansi-default-inverse-bg") end if bold then table.insert(classes, "ansi-bold") end if underline then table.insert(classes, "ansi-underline") end local starttag = "","" end local function codeBlockTrans(e) local converter, fmt if quarto.doc.isFormat('latex') then converter = LaTeXconverter fmt = 'latex' elseif quarto.doc.isFormat('html') then converter = HTMLconverter fmt = 'html' else return end -- not for input cells if e.classes:includes("julia") or e.classes:includes("cell-code") then return end if #e.classes > 0 and not e.classes:includes("julia-stderr") then return end local texenv="OutputCell" local codeclass="" if string.find(e.text, "\u{a35f}\u{2983}") then texenv = "AnsiOutputCell" codeclass = "ansi" end if e.classes:includes("julia-stderr") then texenv = "StderrOutputCell" codeclass = codeclass .. " julia-stderr" -- empty leading space doesn't matter end local out="" -- if string.find(e.text, "\x1b%[") then if string.find(e.text, "\u{a35f}\u{2983}") then local bold = false local underline = false local inverse = false local text = e.text local chunk = "" local fg = nil local bg = nil local starttag = "" local endtag = "" local numbers={} while text ~= "" do numbers = {} -- local s1, e1, c1, d1 = string.find(text, "\x1b%[(.-)([@-~])") local s1, e1, c1, d1 = string.find(text, "\u{a35f}\u{2983}(.-)([@-~])") if s1 then if d1 == "m" then for i in string.gmatch(c1, "([^;]*)") do table.insert(numbers, tonumber(i)) end else quarto.log.warning("Unsupported ANSI sequence ESC["..c1..d1.." ignored\n" ) end chunk, text = text:sub(1, s1-1), text:sub(e1+1) else chunk, text = text, "" end if chunk ~= "" then if bold and type(fg)=="number" and fg<8 then starttag, endtag = converter(fg+8, bg, bold, underline, inverse) else starttag, endtag = converter(fg, bg, bold, underline, inverse) end out = out .. starttag .. chunk .. endtag end while next(numbers) ~= nil do local n = table.remove(numbers, 1) if n == 0 then fg = nil bg = nil bold = false inverse = false underline = false elseif n == 1 then bold = true elseif n == 4 then underline = true elseif n == 5 then bold = true -- 'blinking' elseif n == 7 then inverse = true elseif n == 21 or n == 22 then bold = false elseif n == 24 then underline = false elseif n == 27 then inverse = false elseif n >= 30 and n <= 37 then fg = n - 30 elseif n == 38 then fg = get_extended_color(numbers) elseif n == 39 then fg = nil elseif n >= 40 and n <= 47 then bg = n - 40 elseif n == 48 then bg = get_extended_color(numbers) elseif n == 49 then bg = nil elseif n >= 90 and n <= 97 then fg = n + 8 - 90 elseif n >= 100 and n <= 107 then bg = n + 8 - 100 else quarto.log.warning(string.format("ESC sequence with unknown code %d before:\n",n)) quarto.log.warning(chunk.."\n") end end end else out = e.text end if fmt == 'html' then return pandoc.RawBlock(fmt, '
'..out..'
') end if fmt == 'latex' then return pandoc.RawBlock(fmt, [[\begin{]]..texenv.."}\n"..out.."\n"..[[\end{]].. texenv .. "}") end end -- if div has class 'cell-output-stderr', give CodeBlocks in this div the class 'julia-stderr' local function divStderr(e) if e.classes:includes("cell-output-stderr") then local c = e.content for i,el in pairs(c) do if el.t == 'CodeBlock' then el.classes:insert("julia-stderr") end end return e end end -- repair julia ? output local function divCodeBlockNoHeader1(e) if not e.classes:includes("cell-output") then return end local c = e.content for i, el in pairs(c) do if el.t == 'Header' then el.level = 6 -- elneu = pandoc.Para(el.content) -- c[i] = elneu end if el.t == 'CodeBlock' then if el.classes:includes("jldoctest") then x,i = el.classes:find("jldoctest") el.classes:remove(i) end end end return e end -- test if two divs should be merged local function testmerge(d1, d2) return d1 and d1.t == "Div" and d1.classes:includes("cell-output") and #d1.content == 1 and d2 and d2.t == "Div" and d2.classes:includes("cell-output") and #d2.content == 1 and d1.content[1].t == "CodeBlock" and not d1.classes:includes("cell-output-stderr") and d2.content[1].t == "CodeBlock" and not d2.classes:includes("cell-output-stderr") end -- merge (div (codecell (text1)), div (codecell(text2))) to div(codecell(text1+text2)) local function blockMerge(es) local nl = "" for i = #es-1, 1, -1 do if testmerge(es[i], es[i+1]) then str1 = es[i].content[1].text str2 = es[i+1].content[1].text nl = "\n" if es[i].classes:includes("cell-output-stdout") and es[i+1].classes:includes("cell-output-stdout") then if str1:sub(-1) == "\n" then nl = "" end if str2:sub(1, 1) == "\n" then nl = "" end end es[i].content[1].text = str1 .. nl .. str2 es:remove(i+1) end end return es end local function metaAdd(meta) --for key, val in pairs(PANDOC_READER_OPTIONS) do -- quarto.log.warning(key, val) --end quarto.doc.addHtmlDependency({name='ansicolors', stylesheets = {'resources/css/ansicolor.css'}}) quarto.doc.addHtmlDependency({name='juliamonofont', stylesheets = {'resources/css/juliamono.css'}}) if quarto.doc.isFormat('latex') then quarto.doc.include_file("in-header", "resources/juliainc.tex") end end return { {Div = divStderr}, {Div = divCodeBlockNoHeader1}, {Blocks = blockMerge}, {CodeBlock = codeBlockTrans}, {Meta = metaAdd} }