From 60dc8c3b7ea8e6a0f4fce1c83a28bbc5c2fa9205 Mon Sep 17 00:00:00 2001 From: Sebastian Serth Date: Fri, 2 Sep 2022 16:38:41 +0200 Subject: [PATCH] Apply line-based coloring for output --- app/assets/javascripts/editor/editor.js.erb | 9 +- app/assets/javascripts/editor/evaluation.js | 86 ++++++++++---------- app/assets/stylesheets/base.css.scss | 2 +- app/assets/stylesheets/editor.css.scss | 19 ++--- app/controllers/submissions_controller.rb | 5 +- app/views/exercises/_editor_output.html.slim | 2 +- 6 files changed, 59 insertions(+), 64 deletions(-) diff --git a/app/assets/javascripts/editor/editor.js.erb b/app/assets/javascripts/editor/editor.js.erb index 3df79531..ee564da6 100644 --- a/app/assets/javascripts/editor/editor.js.erb +++ b/app/assets/javascripts/editor/editor.js.erb @@ -78,7 +78,7 @@ var CodeOceanEditor = { if ($('#output-' + index).isPresent()) { return $('#output-' + index); } else { - var element = $('
').attr('id', 'output-' + index);
+            var element = $('
').attr('id', 'output-' + index); $('#output').append(element); return element; } @@ -648,7 +648,7 @@ var CodeOceanEditor = { augmentStacktraceInOutput: function () { if (this.tracepositions_regex) { - $('#output>pre').each($.proxy(function(index, element) { + $('#output > .output-element').each($.proxy(function(index, element) { element = $(element) const text = _.escape(element.text()); @@ -656,16 +656,11 @@ var CodeOceanEditor = { let matches; - // Switch both lines below to enable the output of images and render tags. - // Also consider `printOutput` in evaluation.js - - // let augmented_text = element.text(); let augmented_text = element.html(); while (matches = this.tracepositions_regex.exec(text)) { const frame = $('div.frame[data-filename="' + matches[1] + '"]') if (frame.length > 0) { - // augmented_text = augmented_text.replace(new RegExp(matches[0], 'g'), "" + matches[0] + ""); augmented_text = augmented_text.replace(new RegExp(_.unescape(matches[0]), 'g'), "" + matches[0] + ""); } } diff --git a/app/assets/javascripts/editor/evaluation.js b/app/assets/javascripts/editor/evaluation.js index a4a9311a..23eb9533 100644 --- a/app/assets/javascripts/editor/evaluation.js +++ b/app/assets/javascripts/editor/evaluation.js @@ -189,7 +189,7 @@ CodeOceanEditorEvaluation = { }, clearOutput: function () { - $('#output pre').remove(); + $('#output > .output-element').remove(); CodeOceanEditorTurtle.hideCanvas(); }, @@ -207,50 +207,54 @@ CodeOceanEditorEvaluation = { return; } - if (output.stdout !== undefined && !output.stdout.startsWith("'); + + if (sanitizedStdout !== '') { + if (colorize) { + pre.addClass('text-success'); + } + pre.append(sanitizedStdout) + } + + if (sanitizedStderr !== '') { + if (colorize) { + pre.addClass('text-warning'); + } else { + pre.append('StdErr: '); + } + pre.append(sanitizedStderr); + } + + if (sanitizedStdout === '' && sanitizedStderr === '') { + if (colorize) { + pre.addClass('text-muted'); + } + pre.text($('#output').data('message-no-output')) + } + + element.append(pre); + }, + + sanitizeOutput: function (rawContent) { + let sanitizedContent = _.escape(rawContent).replace(this.nonPrintableRegEx, ""); + + if (rawContent !== undefined && rawContent.trim().startsWith(" document const parsedElement = doc.firstChild.lastChild.firstChild; - const sanitized_img = document.createElement('img'); - sanitized_img.src = parsedElement.src; - output.stdout = sanitized_img.outerHTML; + + if (parsedElement.src.startsWith("data:image")) { + const sanitizedImg = document.createElement('img'); + sanitizedImg.src = parsedElement.src; + sanitizedContent = sanitizedImg.outerHTML; + } } - var element = this.findOrCreateOutputElement(index); - // Switch all four lines below to enable the output of images and render tags. - // Also consider `augmentStacktraceInOutput` in editor.js.erb - if (!colorize) { - if (output.stdout !== undefined && output.stdout !== '') { - output.stdout = output.stdout.replace(this.nonPrintableRegEx, "") - - element.append(output.stdout) - //element.text(element.text() + output.stdout) - } - - if (output.stderr !== undefined && output.stderr !== '') { - output.stderr = output.stderr.replace(this.nonPrintableRegEx, "") - - element.append('StdErr: ' + output.stderr); - //element.text('StdErr: ' + element.text() + output.stderr); - } - - } else if (output.stderr) { - output.stderr = output.stderr.replace(this.nonPrintableRegEx, "") - - element.addClass('text-warning').append(output.stderr); - //element.addClass('text-warning').text(element.text() + output.stderr); - this.QaApiOutputBuffer.stderr += output.stderr; - } else if (output.stdout) { - output.stdout = output.stdout.replace(this.nonPrintableRegEx, "") - - element.addClass('text-success').append(output.stdout); - //element.addClass('text-success').text(element.text() + output.stdout); - this.QaApiOutputBuffer.stdout += output.stdout; - } else { - element.addClass('text-muted').text($('#output').data('message-no-output')); - } + return sanitizedContent; }, getDeadlineInformation: function(deadline, translation_key, otherwise) { diff --git a/app/assets/stylesheets/base.css.scss b/app/assets/stylesheets/base.css.scss index 09235a88..4f5a79f0 100644 --- a/app/assets/stylesheets/base.css.scss +++ b/app/assets/stylesheets/base.css.scss @@ -25,7 +25,7 @@ i.fa-solid, i.fa-regular, i.fa-solid { margin-right: 0.5em; } -pre { +pre, .output-element { background-color: #FAFAFA; margin: 0; padding: .25rem!important; diff --git a/app/assets/stylesheets/editor.css.scss b/app/assets/stylesheets/editor.css.scss index 7d2ae714..9c4a5c7b 100644 --- a/app/assets/stylesheets/editor.css.scss +++ b/app/assets/stylesheets/editor.css.scss @@ -77,20 +77,13 @@ overflow: auto; } -#outputInformation { - #output { - max-height: 500px; - width: 100%; +#output { + white-space: pre; + font-family: var(--bs-font-monospace); + font-size: 14px; + + .output-element { overflow: auto; - margin: 2em 0; - - p { - margin: 0.5em; - } - - pre + pre { - margin-top: 1em; - } } } diff --git a/app/controllers/submissions_controller.rb b/app/controllers/submissions_controller.rb index cdc687a3..2fa21912 100644 --- a/app/controllers/submissions_controller.rb +++ b/app/controllers/submissions_controller.rb @@ -73,6 +73,7 @@ class SubmissionsController < ApplicationController end end + # rubocop:disable Metrics/CyclomaticComplexity def run # These method-local socket variables are required in order to use one socket # in the callbacks of the other socket. As the callbacks for the client socket @@ -167,7 +168,8 @@ class SubmissionsController < ApplicationController @testrun[:status] = :failed "\n#{t('exercises.implement.exit_failure', timestamp: l(Time.zone.now, format: :short), exit_code: exit_code)}" end - send_and_store client_socket, {cmd: :write, stream: :stdout, data: "#{exit_statement}\n"} + stream = @testrun[:status] == :ok ? :stdout : :stderr + send_and_store client_socket, {cmd: :write, stream: stream, data: "#{exit_statement}\n"} if exit_code == 137 send_and_store client_socket, {cmd: :status, status: :out_of_memory} @testrun[:status] = :out_of_memory @@ -194,6 +196,7 @@ class SubmissionsController < ApplicationController ensure save_testrun_output 'run' end + # rubocop:enable Metrics/CyclomaticComplexity: def score hijack do |tubesock| diff --git a/app/views/exercises/_editor_output.html.slim b/app/views/exercises/_editor_output.html.slim index 2997ca3e..c4838cba 100644 --- a/app/views/exercises/_editor_output.html.slim +++ b/app/views/exercises/_editor_output.html.slim @@ -79,7 +79,7 @@ div.d-grid id='output_sidebar_uncollapsed' class='d-none col-sm-12 enforce-botto .heading = t('exercises.implement.error_hints.heading') ul.body.mb-0 #output - pre.overflow-scroll = t('exercises.implement.no_output_yet') + .output-element.overflow-scroll = t('exercises.implement.no_output_yet') - if CodeOcean::Config.new(:code_ocean).read[:flowr][:enabled] && !@embed_options[:disable_hints] && !@embed_options[:hide_test_results] #flowrHint.mb-2.card.text-white.bg-info data-url=CodeOcean::Config.new(:code_ocean).read[:flowr][:url] role='tab' .card-header = t('exercises.implement.flowr.heading')