Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions common/lib/capa/capa/inputtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -832,6 +832,8 @@ def _extra_context(self):
'queue_len': str(self.queue_len),
'queue_msg': self.queue_msg,
'button_enabled': self.button_enabled(),
'matlab_editor_js': '{static_url}js/vendor/CodeMirror/addons/octave.js'.format(
static_url=self.capa_system.STATIC_URL),
}
return extra_context

Expand Down
40 changes: 9 additions & 31 deletions common/lib/capa/capa/templates/codeinput.html
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
<%! from django.utils.translation import ugettext as _ %>
<section id="textbox_${id}" class="textbox">
<section id="textbox_${id}" class="capa_inputtype textbox cminput">
<textarea rows="${rows}" cols="${cols}" name="input_${id}"
aria-label="${_("{programming_language} editor").format(programming_language=mode)}"
aria-describedby="answer_${id}"
id="input_${id}"
tabindex="0"
% if hidden:
data-mode="${mode}"
data-tabsize="${tabsize}"
% if linenumbers:
data-linenums="true"
% endif
% if hidden:
style="display:none;"
% endif
% endif
>${value|h}</textarea>

<div class="grader-status" tabindex="1">
<div class="grader-status" tabindex="-1">
% if status == 'unsubmitted':
<span class="unanswered" style="display:inline-block;" id="status_${id}" aria-describedby="input_${id}"><span class="sr">Status: </span>Unanswered</span>
% elif status == 'correct':
Expand All @@ -34,31 +39,4 @@
<div class="external-grader-message">
${msg|n}
</div>

<script>
// Note: We need to make the area follow the CodeMirror for this to work.
$(function(){
var cm = CodeMirror.fromTextArea(document.getElementById("input_${id}"), {
% if linenumbers == 'true':
lineNumbers: true,
% endif
mode: "${mode}",
matchBrackets: true,
lineWrapping: true,
indentUnit: "${tabsize}",
tabSize: "${tabsize}",
indentWithTabs: false,
extraKeys: {
"Esc": function(cm) {
$('.grader-status').focus();
return false;
},
"Tab": function(cm) {
cm.replaceSelection("${' '*tabsize}", "end");
}
},
smartIndent: false
});
});
</script>
</section>
51 changes: 23 additions & 28 deletions common/lib/capa/capa/templates/matlabinput.html
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
<section id="textbox_${id}" class="textbox">
<textarea rows="${rows}" cols="${cols}" name="input_${id}" aria-describedby="answer_${id}" id="input_${id}"
% if hidden:
style="display:none;"
% endif
<section id="textbox_${id}" class="capa_inputtype cminput">
<div class="script_placeholder" data-src="${matlab_editor_js}"/>
<textarea
rows="${rows}"
cols="${cols}"
name="input_${id}"
aria-describedby="answer_${id}"
id="input_${id}"
data-tabsize="${tabsize}"
data-mode="octave"
% if linenumbers:
data-linenums="true"
% endif
% if hidden:
style="display:none;"
% endif
>${value|h}</textarea>

<div class="grader-status">
<div class="grader-status" tabindex="-1">
% if status == 'unsubmitted':
<span class="unanswered" style="display:inline-block;" id="status_${id}"><span class="sr">Status: </span>Unanswered</span>
% elif status == 'correct':
Expand Down Expand Up @@ -40,28 +51,7 @@
%endif

<script>
// Note: We need to make the area follow the CodeMirror for this to work.
$(function(){
var cm = CodeMirror.fromTextArea(document.getElementById("input_${id}"), {
% if linenumbers == 'true':
lineNumbers: true,
% endif
mode: "matlab",
matchBrackets: true,
lineWrapping: true,
indentUnit: "${tabsize}",
tabSize: "${tabsize}",
indentWithTabs: false,
extraKeys: {
"Tab": function(cm) {
cm.replaceSelection("${' '*tabsize}", "end");
}
},
smartIndent: false
});

$("#textbox_${id}").find('.CodeMirror-scroll').height(${int(13.5*eval(rows))});

var gentle_alert = function (parent_elt, msg) {
if($(parent_elt).find('.capa_alert').length) {
$(parent_elt).find('.capa_alert').remove();
Expand All @@ -80,8 +70,13 @@
input_id = "${id}";

// save the codemirror text to the textarea
cm.save();
// since there could be multiple codemirror instances on the page,
// save all of them.
$('.CodeMirror').each(function(i, el){
el.CodeMirror.save();
});
var input = $("#input_${id}");

// pull out the coded text
submission = input.val();

Expand Down
10 changes: 5 additions & 5 deletions common/lib/capa/capa/tests/test_inputtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,7 @@ def test_rendering(self):
'tabsize': int(self.tabsize),
'button_enabled': True,
'queue_len': '3',
'matlab_editor_js': '/dummy-static/js/vendor/CodeMirror/addons/octave.js',
}

self.assertEqual(context, expected)
Expand Down Expand Up @@ -465,6 +466,7 @@ def test_rendering_with_state(self):
'tabsize': int(self.tabsize),
'button_enabled': True,
'queue_len': '3',
'matlab_editor_js': '/dummy-static/js/vendor/CodeMirror/addons/octave.js',
}

self.assertEqual(context, expected)
Expand Down Expand Up @@ -495,6 +497,7 @@ def test_rendering_when_completed(self):
'tabsize': int(self.tabsize),
'button_enabled': False,
'queue_len': '0',
'matlab_editor_js': '/dummy-static/js/vendor/CodeMirror/addons/octave.js',
}

self.assertEqual(context, expected)
Expand Down Expand Up @@ -524,6 +527,7 @@ def test_rendering_while_queued(self):
'tabsize': int(self.tabsize),
'button_enabled': True,
'queue_len': '1',
'matlab_editor_js': '/dummy-static/js/vendor/CodeMirror/addons/octave.js',
}

self.assertEqual(context, expected)
Expand Down Expand Up @@ -589,11 +593,7 @@ def test_get_html(self):
output = self.the_input.get_html()
self.assertEqual(
etree.tostring(output),
"""<div>{\'status\': \'queued\', \'button_enabled\': True, \'rows\': \'10\', \'queue_len\': \'3\'\
, \'mode\': \'\', \'cols\': \'80\', \'STATIC_URL\': \'/dummy-static/\', \'linenumbers\': \'true\'\
, \'queue_msg\': \'\', \'value\': \'print "good evening"\', \'msg\': u\'Submitted\
. As soon as a response is returned, this message will be replaced by that feedback.\', \'hidden\': \'\'\
, \'id\': \'prob_1_2\', \'tabsize\': 4}</div>"""
"""<div>{\'status\': \'queued\', \'button_enabled\': True, \'rows\': \'10\', \'queue_len\': \'3\', \'mode\': \'\', \'cols\': \'80\', \'STATIC_URL\': \'/dummy-static/\', \'linenumbers\': \'true\', \'queue_msg\': \'\', \'value\': \'print "good evening"\', \'msg\': u\'Submitted. As soon as a response is returned, this message will be replaced by that feedback.\', \'matlab_editor_js\': \'/dummy-static/js/vendor/CodeMirror/addons/octave.js\', \'hidden\': \'\', \'id\': \'prob_1_2\', \'tabsize\': 4}</div>"""
)

# test html, that is correct HTML5 html, but is not parsable by XML parser.
Expand Down
2 changes: 1 addition & 1 deletion common/lib/xmodule/xmodule/css/capa/display.scss
Original file line number Diff line number Diff line change
Expand Up @@ -514,7 +514,7 @@ div.problem {
border: 1px solid black;
font-size: 14px;
line-height: 18px;
resize: both;
resize: none;

.cm-tab {
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAMCAYAAAAkuj5RAAAAAXNSR0IArs4c6QAAAGFJREFUSMft1LsRQFAQheHPowAKoACx3IgEKtaEHujDjORSgWTH/ZOdnZOcM/sgk/kFFWY0qV8foQwS4MKBCS3qR6ixBJvElOobYAtivseIE120FaowJPN75GMu8j/LfMwNjh4HUpwg4LUAAAAASUVORK5CYII=);
Expand Down
25 changes: 25 additions & 0 deletions common/lib/xmodule/xmodule/js/src/capa/display.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,31 @@ class @Problem

return display

cminput: (container) =>
element = $(container).find("textarea")
tabsize = element.data("tabsize")
mode = element.data("mode")
linenumbers = element.data("linenums")
spaces = Array(parseInt(tabsize) + 1).join(" ")
CodeMirror.fromTextArea element[0], {
lineNumbers: linenumbers
indentUnit: tabsize
tabSize: tabsize
mode: mode
matchBrackets: true
lineWrapping: true
indentWithTabs: false
smartIndent: false
extraKeys: {
"Esc": (cm) ->
$(".grader-status").focus()
return false
"Tab": (cm) ->
cm.replaceSelection(spaces, "end")
return false
}
}

inputtypeShowAnswerMethods:
choicegroup: (element, display, answers) =>
element = $(element)
Expand Down
118 changes: 118 additions & 0 deletions common/static/js/vendor/CodeMirror/addons/octave.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
CodeMirror.defineMode("octave", function() {
function wordRegexp(words) {
return new RegExp("^((" + words.join(")|(") + "))\\b");
}

var singleOperators = new RegExp("^[\\+\\-\\*/&|\\^~<>!@'\\\\]");
var singleDelimiters = new RegExp('^[\\(\\[\\{\\},:=;]');
var doubleOperators = new RegExp("^((==)|(~=)|(<=)|(>=)|(<<)|(>>)|(\\.[\\+\\-\\*/\\^\\\\]))");
var doubleDelimiters = new RegExp("^((!=)|(\\+=)|(\\-=)|(\\*=)|(/=)|(&=)|(\\|=)|(\\^=))");
var tripleDelimiters = new RegExp("^((>>=)|(<<=))");
var expressionEnd = new RegExp("^[\\]\\)]");
var identifiers = new RegExp("^[_A-Za-z][_A-Za-z0-9]*");

var builtins = wordRegexp([
'error', 'eval', 'function', 'abs', 'acos', 'atan', 'asin', 'cos',
'cosh', 'exp', 'log', 'prod', 'log10', 'max', 'min', 'sign', 'sin', 'sinh',
'sqrt', 'tan', 'reshape', 'break', 'zeros', 'default', 'margin', 'round', 'ones',
'rand', 'syn', 'ceil', 'floor', 'size', 'clear', 'zeros', 'eye', 'mean', 'std', 'cov',
'det', 'eig', 'inv', 'norm', 'rank', 'trace', 'expm', 'logm', 'sqrtm', 'linspace', 'plot',
'title', 'xlabel', 'ylabel', 'legend', 'text', 'meshgrid', 'mesh', 'num2str'
]);

var keywords = wordRegexp([
'return', 'case', 'switch', 'else', 'elseif', 'end', 'endif', 'endfunction',
'if', 'otherwise', 'do', 'for', 'while', 'try', 'catch', 'classdef', 'properties', 'events',
'methods', 'global', 'persistent', 'endfor', 'endwhile', 'printf', 'disp', 'until', 'continue'
]);


// tokenizers
function tokenTranspose(stream, state) {
if (!stream.sol() && stream.peek() === '\'') {
stream.next();
state.tokenize = tokenBase;
return 'operator';
}
state.tokenize = tokenBase;
return tokenBase(stream, state);
}


function tokenComment(stream, state) {
if (stream.match(/^.*%}/)) {
state.tokenize = tokenBase;
return 'comment';
};
stream.skipToEnd();
return 'comment';
}

function tokenBase(stream, state) {
// whitespaces
if (stream.eatSpace()) return null;

// Handle one line Comments
if (stream.match('%{')){
state.tokenize = tokenComment;
stream.skipToEnd();
return 'comment';
}

if (stream.match(/^(%)|(\.\.\.)/)){
stream.skipToEnd();
return 'comment';
}

// Handle Number Literals
if (stream.match(/^[0-9\.+-]/, false)) {
if (stream.match(/^[+-]?0x[0-9a-fA-F]+[ij]?/)) {
stream.tokenize = tokenBase;
return 'number'; };
if (stream.match(/^[+-]?\d*\.\d+([EeDd][+-]?\d+)?[ij]?/)) { return 'number'; };
if (stream.match(/^[+-]?\d+([EeDd][+-]?\d+)?[ij]?/)) { return 'number'; };
}
if (stream.match(wordRegexp(['nan','NaN','inf','Inf']))) { return 'number'; };

// Handle Strings
if (stream.match(/^"([^"]|(""))*"/)) { return 'string'; } ;
if (stream.match(/^'([^']|(''))*'/)) { return 'string'; } ;

// Handle words
if (stream.match(keywords)) { return 'keyword'; } ;
if (stream.match(builtins)) { return 'builtin'; } ;
if (stream.match(identifiers)) { return 'variable'; } ;

if (stream.match(singleOperators) || stream.match(doubleOperators)) { return 'operator'; };
if (stream.match(singleDelimiters) || stream.match(doubleDelimiters) || stream.match(tripleDelimiters)) { return null; };

if (stream.match(expressionEnd)) {
state.tokenize = tokenTranspose;
return null;
};


// Handle non-detected items
stream.next();
return 'error';
};


return {
startState: function() {
return {
tokenize: tokenBase
};
},

token: function(stream, state) {
var style = state.tokenize(stream, state);
if (style === 'number' || style === 'variable'){
state.tokenize = tokenTranspose;
}
return style;
}
};
});

CodeMirror.defineMIME("text/x-octave", "octave");