diff options
-rw-r--r-- | css/codeq.css | 58 | ||||
-rw-r--r-- | index.html | 18 | ||||
-rw-r--r-- | js/codeq/login.js | 112 | ||||
-rw-r--r-- | js/codeq/mainScreen.js | 126 | ||||
-rw-r--r-- | js/codeq/prolog.js | 450 | ||||
-rw-r--r-- | js/codeq/prologPythonLib.js | 341 | ||||
-rw-r--r-- | js/codeq/python.js | 393 | ||||
-rw-r--r-- | js/codeq/startup.js | 6 | ||||
-rw-r--r-- | js/codeq/stateMachine.js | 22 | ||||
-rw-r--r-- | js/prolog.js | 5 | ||||
-rw-r--r-- | js/python.js | 3 |
11 files changed, 1521 insertions, 13 deletions
diff --git a/css/codeq.css b/css/codeq.css index 7172ef8..cfb9286 100644 --- a/css/codeq.css +++ b/css/codeq.css @@ -38,6 +38,16 @@ body { border: 0; } +/**** animations *****/ +.transition{ + -moz-transition: all 1s ease; + -webkit-transition: all 1s ease; + -o-transition: all 1s ease; + -ms-transition: all 1s ease; + transition: all 1s ease; +} + + /***** responsiveness *****/ /* lg */ @media (min-width: 1200px) { @@ -49,6 +59,30 @@ body { overflow: auto; min-height: 100%; height: 100%; } + + .block-focus { + overflow: auto; + min-height: 100%; height: 100%; + min-width: 34%; width: 34%; + } + + .block-less-height{/*these names were made for the 'md' variant but need to be the same here for the javascript code to resize them when the focus changes*/ + overflow: auto; + min-height: 100%; height: 100%; + min-width: 22%; width: 22%; + } + + .block-less-width{/*these names were made for the 'md' variant but need to be the same here for the javascript code to resize them when the focus changes*/ + overflow: auto; + min-height: 100%; height: 100%; + min-width: 22%; width: 22%; + } + + .block-less-everything{/*these names were made for the 'md' variant but need to be the same here for the javascript code to resize them when the focus changes*/ + overflow: auto; + min-height: 100%; height: 100%; + min-width: 22%; width: 22%; + } } /* md */ @@ -61,6 +95,30 @@ body { overflow: auto; min-height: 50%; height: 50%; } + + .block-focus { + overflow: auto; + min-height: 60%; height: 60%; + min-width: 60%; width: 60%; + } + + .block-less-height{ + overflow: auto; + min-height: 40%; height: 40%; + min-width: 60%; width: 60%; + } + + .block-less-width{ + overflow: auto; + min-height: 60%; height: 60%; + min-width: 40%; width: 40%; + } + + .block-less-everything{ + overflow: auto; + min-height: 40%; height: 40%; + min-width: 40%; width: 40%; + } } /* sm */ @@ -82,11 +82,11 @@ <div class="container-fluid" id="screen_prolog" style="display: none;"> <div class="row"> - <div class="col-lg-3 col-md-6 col-sm-12 block"> + <div class="col-lg-3 col-md-6 col-sm-12 block" id="description_outer_div"> <div id="description"></div> <div class="block-label">Instructions</div> </div> - <div class="col-lg-3 col-md-6 col-sm-12 block"> + <div class="col-lg-3 col-md-6 col-sm-12 block" id="code_editor_outer_div"> <nav class="navbar navbar-default" id="block-toolbar"> <div class="container-fluid"> <button type="button" class="btn btn-default navbar-btn" id="btn_code_hint">Hint</button> @@ -96,11 +96,11 @@ <div id="code_editor"></div> <div class="block-label">Code</div> </div> - <div class="col-lg-3 col-md-6 col-sm-12 block"> + <div class="col-lg-3 col-md-6 col-sm-12 block" id="console_outer_div"> <div id="console"></div> <div class="block-label">Console</div> </div> - <div class="col-lg-3 col-md-6 col-sm-12 block"> + <div class="col-lg-3 col-md-6 col-sm-12 block" id="info_outer_div"> <div id="info"></div> <div class="block-label">Hints</div> </div> @@ -129,12 +129,18 @@ <script src="js/codemirror/show-hint.js"></script> <!-- codeq app --> <script src="js/codeq.js"></script> + <script src="js/codeq/stateMachine.js"></script> <script src="js/codeq/comms.js"></script> <script src="js/codeq/console.js"></script> <script src="js/codeq/hint.js"></script> <script src="js/def_parser.js"></script> - <script src="js/prolog.js"></script> - <script src="js/python.js"></script> + <!-- <script src="js/codeq/prologPythonLib.js"></script> --> + <script src="js/codeq/prolog.js"></script> + <script src="js/codeq/python.js"></script> + <!-- <script src="js/prolog.js"></script> --> + <!-- <script src="js/python.js"></script> --> + <script src="js/codeq/login.js"></script> + <script src="js/codeq/mainScreen.js"></script> <script src="js/codeq/startup.js"></script> </body> </html> diff --git a/js/codeq/login.js b/js/codeq/login.js new file mode 100644 index 0000000..5c93469 --- /dev/null +++ b/js/codeq/login.js @@ -0,0 +1,112 @@ +/** + * Created by robert on 9/17/15. + */ + +(function(){ + var loginFun = function(){ + var identifier = $('#problem_group').val().split('/'), + problem = $('#problems').val(); + if (identifier.length < 2) alert('Choose a problem group'); + else if (!problem) alert('Choose a problem'); + else { + $('#disabled').css('display', ''); + codeq.comms.login($('#username').val(), $('#password').val()) + .then(function (data) { + $('#disabled').css('display', 'none'); + if (data.code !== 0) throw new Error('Login failed, code: ' + data.code + ', message: ' + data.message); + }) + .then(function () { + return codeq.comms.getProblem(identifier[0], identifier[1], problem); + }) + .then(function (data) { + if (data.code !== 0) throw new Error('Failed to obtain problem data, code: ' + data.code + ', message: ' + data.message); + $('#disabled').css('display', 'none'); + switch (identifier[0]) { + case 'prolog': + // TODO: assignment to window for debug only + //$('#screen_login').css('display', 'none'); + //$('#screen_prolog').css('display', ''); + codeq.globalStateMachine.transition('prolog', data); + //window.phandler = codeq.createProgrammingLanguageHandler('prolog',data.data,codeq.makePrologTerminalHandler);//codeq.createPrologHandler(data.data); + break; + case 'python': + // TODO: assignment to window for debug only + //$('#screen_login').css('display', 'none'); + //$('#screen_prolog').css('display', ''); + codeq.globalStateMachine.transition('python', data); + //window.phandler = codeq.createProgrammingLanguageHandler('python',data.data,codeq.makePythonTerminalHandler);//codeq.createPythonHandler(data.data); + break; + default: + alert('Unknown language: ' + identifier[0]); + break; + } + }) + .fail(function (reason) { + $('#disabled').css('display', 'none'); + alert('Login request failed: ' + reason); + }) + .done(); + } + }; + codeq.globalStateMachine.register('login',{ + 'enter': function(){ + codeq.comms.connect().then(function () { + return codeq.comms.send({'action': 'list_problems'});//currently problem list and the actual login are still in the same state + }).then( + function success(data) { + var i, groups, group, problems, problem, first_group, + jqGroup = $('#problem_group'), + jqProblems = $('#problems'), + html = [], + mapping = {}, + onGroupChange = function () { + var problems = mapping[jqGroup.val()], + html = [], + i, p; + if (problems) { + for (i = 0; i < problems.length; i++) { + p = problems[i]; + html.push('<option value="', p.identifier, '">', p.name, '</option>\n') + } + } + jqProblems.html(html.join('')); + }; + + if (data && (data.code === 0)) { + $('#disabled').css('display', 'none'); + groups = data.problems; + for (i = 0; i < groups.length; i++) { + group = groups[i]; + var identifier = group.identifier.language + '/' + group.identifier.group; + var name = group.name.language + ': ' + group.name.group; + html.push('<option value="', identifier, '">', name, '</option>\n'); + mapping[identifier] = group.problems; + } + jqGroup.html(html.join('')); + first_group = html[1]; + html = null; + + jqGroup.on('click', onGroupChange); + jqGroup.val(first_group); + onGroupChange(); + + $("#submit").on('click', loginFun); + } + else { + $('#disabled').css('cursor', ''); + alert('Obtaining list of problems failed: code=' + data.code + ', reason=' + data.message); + } + }, + + function failure(reason) { + $('#disabled').css('cursor', ''); + alert('Request to obtain list of problems failed: ' + reason); + } + ).done(); + }, + 'exit' : function(){ + $("#submit").off('click', loginFun); + $("#screen_login").css('display', 'none'); + } + }); +})();
\ No newline at end of file diff --git a/js/codeq/mainScreen.js b/js/codeq/mainScreen.js new file mode 100644 index 0000000..e842ee3 --- /dev/null +++ b/js/codeq/mainScreen.js @@ -0,0 +1,126 @@ +/** + * Created by robert on 9/17/15. + */ + +(function() { + var problems,//this will the actual (sub)state machine + stateNameTag = 'stateName'; + + var divs = {}; + divs['description'] = $('div.col-lg-3.col-md-6.col-sm-12:has(#description)');//lets actually find the needed divs for later use + divs['code'] = $('div.col-lg-3.col-md-6.col-sm-12:has(#code_editor)'); + divs['console'] = $('div.col-lg-3.col-md-6.col-sm-12:has(#console)');//named 'consoleDiv', because 'console' is already in use + divs['info'] = $('div.col-lg-3.col-md-6.col-sm-12:has(#info)'); + + divs['description'].data(stateNameTag, 'description'); + divs['code'].data(stateNameTag, 'code'); + divs['console'].data(stateNameTag, 'console'); + divs['info'].data(stateNameTag, 'info'); + + var eventName = 'mousedown',//event name of the event which will trigger the transition between these substates + mouseDownEventFunction = function () { + problems.transition($(this).data(stateNameTag)); + }, + removeListenersFromDivs = function () { + $.each(divs, function (i, value) { + value.off(eventName, mouseDownEventFunction); + }); + }, + removeBlockClassesFromDivs = function () { + $.each(divs, function (i, value) { + value.removeClass('block'); + }); + }, + removeChangedClassesFromDivs = function () { + $.each(divs, function (i, value) { + value.removeClass('block-focus block-less-width block-less-height block-less-everything').addClass('block');//these class names were chosen for the view where the screen is partitioned in 4 quarters (2 by 2) + }); + }, + setTransitionsBetweenDivs = function (current) { + $.each(divs, function (key, value) { + if (current !== key) value.on(eventName, mouseDownEventFunction); + }); + }, + addClassesToDivs = function (divsWithClasses) { + $.each(divsWithClasses, function (divName, className) { + divs[divName].addClass(className); + }); + }, + substates = { + 'description': { + 'enter': function () { + removeBlockClassesFromDivs(); + addClassesToDivs({ + 'description': 'block-focus', + 'code': 'block-less-width', + 'console': 'block-less-height', + 'info': 'block-less-everything' + }); + setTransitionsBetweenDivs('description'); + }, + 'exit': function () { + removeChangedClassesFromDivs(); + removeListenersFromDivs(); + } + }, + 'code': { + 'enter': function () { + removeBlockClassesFromDivs(); + addClassesToDivs({ + 'description': 'block-less-width', + 'code': 'block-focus', + 'console': 'block-less-everything', + 'info': 'block-less-height' + }); + setTransitionsBetweenDivs('code'); + }, + 'exit': function () { + removeChangedClassesFromDivs(); + removeListenersFromDivs(); + } + }, + 'info': { + 'enter': function () { + removeBlockClassesFromDivs(); + addClassesToDivs({ + 'description': 'block-less-everything', + 'code': 'block-less-height', + 'console': 'block-less-width', + 'info': 'block-focus' + }); + setTransitionsBetweenDivs('info'); + }, + 'exit': function () { + removeChangedClassesFromDivs(); + removeListenersFromDivs(); + } + }, + 'console': { + 'enter': function () { + removeBlockClassesFromDivs(); + addClassesToDivs({ + 'description': 'block-less-height', + 'code': 'block-less-everything', + 'console': 'block-focus', + 'info': 'block-less-width' + }); + setTransitionsBetweenDivs('console'); + }, + 'exit': function () { + removeChangedClassesFromDivs(); + removeListenersFromDivs(); + } + } + }; + codeq.globalStateMachine.register('main', { + 'enter': function () { + $('#screen_prolog').css('display', ''); + problems = codeq.makeStateMachine(substates); + problems.transition(divs['description'].data(stateNameTag)); + }, + 'exit': function () { + $('#screen_prolog').css('display', 'none'); + problems.destroy(); + } + }); +})();
\ No newline at end of file diff --git a/js/codeq/prolog.js b/js/codeq/prolog.js new file mode 100644 index 0000000..1978ba4 --- /dev/null +++ b/js/codeq/prolog.js @@ -0,0 +1,450 @@ +/** + * Created by robert on 9/17/15. + * + * The prolog state of the state machine. When it is entered it'll prepare the console and code editor and load a sub-state machine which represents the 4 different parts fo the screen. + */ + +(function() { + var subScreens,//this will the actual (sub)state machine + stateNameTag = 'stateName';//a tag for data which is added to some html elements + + //get the divs which are the main elements being change on transitions between states in the sub-state machine + var divs = {}; + divs['description'] = $('#description_outer_div'); + divs['code'] = $('#code_editor_outer_div'); + divs['console'] = $('#console_outer_div'); + divs['info'] = $('#info_outer_div'); + + //these tags connect the div to their respective state in the sub-state machine + divs['description'].data(stateNameTag, 'description'); + divs['code'].data(stateNameTag, 'code'); + divs['console'].data(stateNameTag, 'console'); + divs['info'].data(stateNameTag, 'info'); + + var transitionEventName = 'mousedown',//event name of the event which will trigger the transition between these substates - the most common transition at least (there are some corner cases on the hint and test buttons -> see the code below) + /** + * the general function which should be called on the mousedown event to trigger the transition between states + */ + mouseDownEventFunction = function () { + subScreens.transition($(this).data(stateNameTag)); + }, + /** + * removes the above function from the divs + */ + removeListenersFromDivs = function () { + $.each(divs, function (i, value) { + value.off(transitionEventName, mouseDownEventFunction); + }); + }, + /** + * removes the 'block' class from all divs we need to change when we change the current state + */ + removeBlockClassesFromDivs = function () { + $.each(divs, function (i, value) { + value.removeClass('block'); + }); + }, + /** + * removes all those special classes from the divs, which are set when a new state is entered and it also adds the 'block' class back + */ + removeChangedClassesFromDivs = function () { + $.each(divs, function (i, value) { + value.removeClass('block-focus block-less-width block-less-height block-less-everything').addClass('block');//these class names were chosen for the view where the screen is partitioned in 4 quarters (2 by 2) + }); + }, + /** + * + * @param current the key of the state ('description','code','hint','console'), which is currently active, because it must not have a transition set + */ + setTransitionsBetweenDivs = function (current) { + $.each(divs, function (key, value) { + if (current !== key) value.on(transitionEventName, mouseDownEventFunction); + }); + }, + /** + * + * @param divsWithClasses a object where we have the keys of the divs object paired with the class name we want to assign to them e.g. : + * { + 'description': 'block-focus', + 'code': 'block-less-width', + 'console': 'block-less-height', + 'info': 'block-less-everything' + } + */ + addClassesToDivs = function (divsWithClasses) { + $.each(divsWithClasses, function (divName, className) { + divs[divName].addClass(className); + }); + }, + /** + * the buttons (hint and test) will have to 'eat' the mousedown event to prevent some weird transitions. + * Because a click on them won't trigger the transition to the code part, but will transition to the hints part. + * + * @param event + */ + mouseDownEventIgnoreFun = function(event){ + event.stopPropagation(); + }, + /** + * The transition from the buttons to the hints screen will be triggered with this function + */ + clickListenerTransitionFun = function(){ + subScreens.transition(divs['info'].data(stateNameTag)); + }, + /** + * add the above function to the buttons + */ + addClickListenerTranstions = function(){ + $('#btn_code_hint').on('click',clickListenerTransitionFun); + $('#btn_code_test').on('click',clickListenerTransitionFun); + + $('#btn_code_hint').on(transitionEventName,mouseDownEventIgnoreFun); + $('#btn_code_test').on(transitionEventName,mouseDownEventIgnoreFun); + }, + /** + * and a function to remove it + */ + removeClickListenerTransition = function(){ + $('#btn_code_hint').off('click',clickListenerTransitionFun); + $('#btn_code_test').off('click',clickListenerTransitionFun); + + $('#btn_code_hint').off(transitionEventName,mouseDownEventIgnoreFun); + $('#btn_code_test').off(transitionEventName,mouseDownEventIgnoreFun); + }, + substates = { + 'description': { + 'enter': function () { + removeBlockClassesFromDivs(); + addClassesToDivs({ + 'description': 'block-focus', + 'code': 'block-less-width', + 'console': 'block-less-height', + 'info': 'block-less-everything' + }); + setTransitionsBetweenDivs('description'); + + //and set up the buttons + addClickListenerTranstions(); + }, + 'exit': function () { + removeChangedClassesFromDivs(); + removeListenersFromDivs(); + removeClickListenerTransition(); + } + }, + 'code': { + 'enter': function () { + removeBlockClassesFromDivs(); + addClassesToDivs({ + 'description': 'block-less-width', + 'code': 'block-focus', + 'console': 'block-less-everything', + 'info': 'block-less-height' + }); + setTransitionsBetweenDivs('code'); + + //and set up the buttons + addClickListenerTranstions(); + }, + 'exit': function () { + removeChangedClassesFromDivs(); + removeListenersFromDivs(); + removeClickListenerTransition(); + } + }, + 'info': { + 'enter': function () { + removeBlockClassesFromDivs(); + addClassesToDivs({ + 'description': 'block-less-everything', + 'code': 'block-less-height', + 'console': 'block-less-width', + 'info': 'block-focus' + }); + setTransitionsBetweenDivs('info'); + + //and set up the buttons + addClickListenerTranstions(); + }, + 'exit': function () { + removeChangedClassesFromDivs(); + removeListenersFromDivs(); + removeClickListenerTransition(); + } + }, + 'console': { + 'enter': function () { + removeBlockClassesFromDivs(); + addClassesToDivs({ + 'description': 'block-less-height', + 'code': 'block-less-everything', + 'console': 'block-focus', + 'info': 'block-less-width' + }); + setTransitionsBetweenDivs('console'); + + //and set up the buttons + addClickListenerTranstions(); + }, + 'exit': function () { + removeChangedClassesFromDivs(); + removeListenersFromDivs(); + removeClickListenerTransition(); + } + } + }; + var prologHandler;//created when we enter the prolog state and destroyed once we leave it + codeq.globalStateMachine.register('prolog', { + 'enter': function (data) { + $('#disabled').css('display', ''); + $('#screen_prolog').css('display', '');//we have to show the screen now so the code editor shopws its initial values correctly + prologHandler = createPrologHandler(data.data)//codeq.createProgrammingLanguageHandler('prolog',data.data,codeq.makePrologTerminalHandler); + subScreens = codeq.makeStateMachine(substates); + subScreens.transition(divs['description'].data(stateNameTag)); + Q.delay(100).then(function(){ + $('div.col-lg-3.col-md-6.col-sm-12').addClass('transition');//for smooth animations - need to be delayed, because otherwise we get some weird "animations" while the page is loading + }).done(); + + $('#disabled').css('display', 'none'); + }, + 'exit': function () { + $('#screen_prolog').css('display', 'none'); + $('div.col-lg-3.col-md-6.col-sm-12').removeClass('transition'); + prologHandler.destroy(); + subScreens.destroy(); + } + }); + + // a constant + var firstCharacterPos = {'line': 0, 'ch': 0}; + + var makePrologTerminalHandler = function (jqConsole, editor, problem_id, activityHandler) { + var promptMode = true, // default: query composition; alternative: query result browsing + terminal = codeq.makeConsole(jqConsole, { + 'greeting': 'CodeQ Prolog terminal proxy' + }), + tcs = function terminalCommandSuccess (data) { + var t, lines, i; + if (data.code === 0) { + t = data.terminal; + terminal.append(t.messages.join('\n'), 'output'); + promptMode = !t.have_more; + } + else { + terminal.append(data.message, 'error'); + promptMode = true; + } + if (promptMode) { + terminal.setLineBuffered(); + terminal.append('.\n?- ', 'output'); + } + }, + tcf = function terminalCommandFailed (error) { + promptMode = true; + terminal.setLineBuffered(); + terminal.append(error + '\n', 'error'); + terminal.append('?- ', 'output'); + }; + + terminal.onKeypress = function (c) { + if (promptMode) return c; // query composition: return the character unchanged + switch (c) { + case ' ': + case ';': + case '\n': + return ';'; // show next answer on space, semicolon or enter + default: + return '.'; // everything else: stop searching for answers + } + }; + + terminal.onInput = function (command) { + if (promptMode) { + promptMode = false; + terminal.setNotBuffered(); + return codeq.comms.sendQuery({ + 'problem_id': problem_id, + 'step': 'run', + 'program': editor.getDoc().getValue(), + 'query': command, + 'trace': activityHandler.addAndPurge({'typ': 'slv', 'qry': command}) + }, problem_id).then(tcs, tcf); + } + else { + terminal.append('\n', 'input'); + if (command == ';') { + // show next answer + return codeq.comms.sendQuery({ + 'problem_id': problem_id, + 'step': 'next', + 'trace': activityHandler.addAndPurge({'typ': 'nxt'}) + }, problem_id).then(tcs, tcf); + } + else { + // stop searching for answers + return codeq.comms.sendQuery({ + 'problem_id': problem_id, + 'step': 'end', + 'trace': activityHandler.addAndPurge({'typ': 'stp'}) + }, problem_id).then(tcs, tcf); + } + + } + }; + + terminal.leftmostCol = 3; + terminal.append('?- ', 'output'); + + return terminal; + }; + + var makeActivityHandler = function (editor, problem_id) { + var lastActivityMillis = Date.now(), + deltaActivityMillis = function deltaActivityMillisFunc () { + var now = Date.now(), + dt = now - lastActivityMillis; + lastActivityMillis = now; + return dt; + }, + queue = [], + ts = null, + timer = function () { + var promise; + ts = null; + if (queue.length === 0) return Q(true); + promise = codeq.comms.sendActivity(queue, editor.getDoc().getValue(), problem_id); + queue.length = 0; + return promise; + }, + flush = function () { + clearTimeout(ts); + return timer(); + }; + + return { + 'queueTrace': function (trace) { + trace['dt'] = deltaActivityMillis(); + queue.push(trace); + if (ts === null) ts = setTimeout(timer, 10000); // flush every 10 seconds + return this; + }, + 'flush': flush, + 'addAndPurge': function (trace) { + var accumulatedTrace = queue; + queue = []; + trace['dt'] = deltaActivityMillis(); + accumulatedTrace.push(trace); + if (ts !== null) { + clearTimeout(ts); + ts = null; + } + return accumulatedTrace; + } + }; + }; + + /** + * Creates a new handler for the given Prolog assignment definition. + * + * @param {PrologTaskDef} info + * @returns {{destroy: Function, processServerHints: Function}} + */ + createPrologHandler = function (info) { + var problem = info.problem, + jqDescription = $('#description'), + jqEditor = $('#code_editor'), + jqConsole = $('#console'), + jqHints = $('#info'), + editor = CodeMirror(jqEditor[0], { cursorHeight: 0.85, lineNumbers: true, matchBrackets: true }), + activityHandler = makeActivityHandler(editor, problem.id), + terminal = makePrologTerminalHandler(jqConsole, editor, problem.id, activityHandler), + hinter = codeq.makeHinter(jqHints, jqEditor, editor, problem.hint); + + editor.setValue(info.solution); + $('#title').text(problem.slug); + jqDescription.html(problem.description); + + editor.on('change', function (instance, changeObj) { + var doc = editor.getDoc(), + pos = codeq.codePointCount(doc.getRange(firstCharacterPos, changeObj.from)); + + if (changeObj.removed) { + activityHandler.queueTrace({'typ': 'rm', 'off': pos, 'len': codeq.codePointCount(changeObj.removed)}); + } + + if (changeObj.text) { + activityHandler.queueTrace({'typ': 'ins', 'off': pos, 'txt': changeObj.text}); + } + }); + + $('#btn_code_hint').on('click', function () { + terminal.append('hint.\n', 'input'); + terminal.inputDisable(); + var doc = editor.getDoc(); + codeq.comms.sendHint({ + 'language': 'prolog', + 'program': editor.getDoc().getValue(), + 'problem_id': problem.id + }) + .then( + function hintSuccess(data) { + if (data.code === 0) + hinter.handle(data.hints); + else + terminal.append(data.message + '\n', 'error'); + }, + function hintFailed (error) { + terminal.append(error + '\n', 'error'); + } + ) + .fin(function () { + terminal.inputEnable(); + terminal.append('?- ', 'output'); + }) + .done(); + }); + $('#btn_code_test').on('click', function () { + terminal.append('test.\n', 'input'); + terminal.inputDisable(); + var doc = editor.getDoc(); + codeq.comms.sendTest({ + 'language': 'prolog', + 'program': editor.getDoc().getValue(), + 'problem_id': problem.id + }) + .then( + function testSuccess(data) { + if (data.code === 0) + hinter.handle(data.hints); + else + terminal.append(data.message + '\n', 'error'); + }, + function testFailed (error) { + terminal.append(error + '\n', 'error'); + } + ) + .fin(function () { + terminal.inputEnable(); + terminal.append('?- ', 'output'); + }) + .done(); + }); + + return { + destroy: function () { + $('#btn_code_hint').off('click'); + $('#btn_code_test').off('click'); + editor.off('change'); + hinter.destroy(); + terminal.destroy(); + jqDescription.empty(); + jqEditor.empty(); // TODO: perhaps you do not want to "free" the editor, just empty it + jqConsole.empty(); // TODO: the same with the console + jqDescription = null; + jqEditor = null; + jqConsole = null; + jqHints = null; + } + }; + }; +})();
\ No newline at end of file diff --git a/js/codeq/prologPythonLib.js b/js/codeq/prologPythonLib.js new file mode 100644 index 0000000..400a56e --- /dev/null +++ b/js/codeq/prologPythonLib.js @@ -0,0 +1,341 @@ +/** + * Created by robert on 9/15/15. + */ + +/** + * this is currently not used - I made this common python/prolog library a bit to early, but I don't want to delete everything, so I'll leave it for now in case it can be used later without to many changes + * + * + * + * @param editor + * @param problem_id + * @returns {{queueTrace: Function, flush: Function, addAndPurge: Function}} + */ + +codeq.makeActivityHandler = function (editor, problem_id) { + var lastActivityMillis = Date.now(), + deltaActivityMillis = function deltaActivityMillisFunc () { + var now = Date.now(), + dt = now - lastActivityMillis; + lastActivityMillis = now; + return dt; + }, + queue = [], + ts = null, + timer = function () { + var promise; + ts = null; + if (queue.length === 0) return Q(true); + promise = codeq.comms.sendActivity(queue, editor.getDoc().getValue(), problem_id); + queue.length = 0; + return promise; + }, + flush = function () { + clearTimeout(ts); + return timer(); + }; + + return { + 'queueTrace': function (trace) { + trace['dt'] = deltaActivityMillis(); + queue.push(trace); + if (ts === null) setTimeout(timer, 10000); // flush every 10 seconds + }, + 'flush': flush, + 'addAndPurge': function (trace) { + var accumulatedTrace = queue; + queue = []; + trace['dt'] = deltaActivityMillis(); + accumulatedTrace.push(trace); + if (ts !== null) { + clearTimeout(ts); + ts = null; + } + return accumulatedTrace; + } + }; +}; + +/** + * Creates a new handler for the given Programming language assignment definition. + * + * @param {string} language - either 'prolog' or 'python' + * @param {PrologTaskDef} info + * @param {function} makeTerminalHandlerFun - either the makePythonTerminalHandler or makePrologTerminalHandler function + * @returns {{destroy: Function, processServerHints: Function}} + */ +codeq.createProgrammingLanguageHandler = function (language,info,makeTerminalHandlerFun) { + var firstCharacterPos = {'line': 0, 'ch': 0}; + + var problem = info.problem, + jqDescription = $('#description'), + jqEditor = $('#code_editor'), + jqConsole = $('#console'), + jqHints = $('#info'), + editor = CodeMirror(jqEditor[0], { cursorHeight: 0.85, lineNumbers: true, matchBrackets: true }), + activityHandler = codeq.makeActivityHandler(editor, problem.id), + terminal = makeTerminalHandlerFun(jqConsole, editor, problem.id, activityHandler), + hintDefs = problem.hint, + hintCounter = 0, // for generating unique class-names + hintCleaners = [], + clearHints = function () { + var i; + for (i = hintCleaners.length - 1; i >= 0; i--) { + hintCleaners[i](); + } + hintCleaners.length = 0; + hintCounter = 0; + }, + addMark = function (start, end) { + var posStart = editor.posFromIndex(start), + posEnd = editor.posFromIndex(end), + doc = editor.getDoc(), + mark = doc.markText(posStart, posEnd, {className: 'editor-mark _emark_' + hintCounter}), + result = {start: posStart, end: posEnd, mark: mark, className: '_emark_' + hintCounter}; + hintCleaners.push(function () { mark.clear(); mark = null; doc = null; result.mark = null; }); + hintCounter++; + return result; + }, + processTemplate = function (template, args) { + if (!args) + return template; + return template.replace(/\[%=(\w+)%\]/g, function(match, name) { + return args[name]; + }); + }, + hintHandlers = { + 'static': function (type, template, serverHint) { + codeq.log.debug('Processing static hint'); + var message = processTemplate(template, serverHint.args); + jqHints.append('<div class="hint-static">' + message + '</div>'); + // no hint cleaner here, a static hint remains on the screen + }, + 'popup': function (type, template, serverHint) { + codeq.log.debug('Processing popup hint'); + var message = processTemplate(template, serverHint.args), + mark = addMark(serverHint.start, serverHint.end), // add the mark + jqMark = jqEditor.find('.' + mark.className); + /* jqPopup = null, + onBlur = function () { + codeq.log.debug('Removing popup'); + if (jqPopup) { + jqPopup.off('blur', onBlur); + jqPopup.remove(); + jqPopup = null; + } + }; + + window.jqMark = jqMark; // TODO: DEBUG + + jqMark.on('click', function () { + if (jqPopup) return; + codeq.log.debug('Showing popup'); + var pos = mark.mark.find(), // results in {from: {line: number, ch: number}, to: {line: number, ch: number}} + left = pos.from.ch < pos.to.ch ? pos.from.ch : pos.to.ch, + down = pos.from.line < pos.to.line ? pos.to.line : pos.from.line; + jqPopup = $('<div style="position: absolute;" class="editor-popup ' + mark.className + '"></div>').html(template); + editor.addWidget({line: down, ch: left}, jqPopup[0], true); + jqPopup = jqEditor.find('.editor-popup.' + mark.className); + setTimeout(function () {jqPopup.trigger('focus'); jqPopup.on('click', onBlur);}, 50); // event handlers can be registered only when the DOM elements is part of the document + window.jqPopup = jqPopup; // TODO: DEBUG + }); + + hintCleaners.push(function () { + if (jqPopup) { + jqPopup.off('blur', onBlur); + jqPopup.remove(); + jqPopup = null; + } + if (jqMark) { + jqMark = null; + } + mark = null; + });*/ + + jqMark.popover({content: message, html: true, placement: 'auto bottom', trigger: 'hover focus click', container: 'body'}); + hintCleaners.push(function () { if (jqMark) { jqMark.popover('destroy'); jqMark = null; } }); + + mark.mark.on('', function () {}); + }, + 'dropdown': function (type, template, serverHint) { + codeq.log.debug('Processing dropdown hint'); + var completion = null, // the completion object, created in showHint() + close = function () { + if (completion) { + completion.close(); + completion = null; + } + }; + + if ((editor.listSelections().length > 1) || editor.somethingSelected()) { + // showHint() doesn't work if a selection is activeparts + } + + editor.showHint({ + hint: function () { + var hints = { + list: serverHint.choices, + from: editor.posFromIndex(serverHint.start), + to: editor.posFromIndex(serverHint.end) + }; + completion = editor.state.completionActive; + return hints; + }, + completeOnSingleClick: true, + completeSingle: false + }); + + hintCleaners.push(close); + } + }; + + editor.setValue(info.solution); + $('#title').text(problem.slug); + jqDescription.html(problem.description); + + editor.on('change', function (instance, changeObj) { + var doc = editor.getDoc(), + pos = codeq.codePointCount(doc.getRange(firstCharacterPos, changeObj.from)); + + if (changeObj.removed) { + activityHandler.queueTrace({'typ': 'rm', 'off': pos, 'len': codeq.codePointCount(changeObj.removed)}); + } + + if (changeObj.text) { + activityHandler.queueTrace({'typ': 'ins', 'off': pos, 'txt': changeObj.text}); + } + }); + + var handler = { + destroy: function () { + terminal.destroy(); + jqDescription.empty(); + jqEditor.empty(); // TODO: perhaps you do not want to "free" the editor, just empty it + jqConsole.empty(); // TODO: the same with the console + jqHints.empty(); // TODO: just make static references to these + jqDescription = null; + jqEditor = null; + jqConsole = null; + jqHints = null; + }, + + /** + * Processes and display appropriately the server hints. + * TODO: sort hints so static and popup hints come first, and a (single) drop-down hint last + * + * @param {ServerHint[]} serverHints an array of hints from the server + */ + processServerHints: function (serverHints) { + var n = serverHints.length, + /** number */ i, + /** ServerHint */ serverHint, + /** HintDefinition */ hintDef, + hintType, hintTemplate, t, fn; + clearHints(); + for (i = 0; i < n; i++) { + serverHint = serverHints[i]; + hintDef = hintDefs[serverHint.id]; + if (serverHint.indices) { + indices = serverHint.indices + for (i = 0; i < indices.length; i++) { + hintDef = hintDef[indices[i]]; + if (!hintDef) + break; + } + } + if (!hintDef) { + codeq.log.error('Undefined hint ' + serverHint.id + ' with indices ' + serverHint.indices); + continue; + } + t = typeof hintDef; + if (t === 'object') { + hintType = hintDef.type; + hintTemplate = hintDef.message; + } + else if (t === 'string') { + hintType = 'static'; + hintTemplate = hintDef; + } + else { + codeq.log.error('Unsupported hint definition: ' + t); + continue; + } + + fn = hintHandlers[hintType]; + if (!fn) codeq.log.error('Unsupported hint type: ' + hintType); + else fn(hintType, hintTemplate, serverHint); + } + } + }; + + var jqButtons = $('#block-toolbar button'); +// $(jqButtons.get(0)).on('click', function () { handler.processServerHints([{id:'x_must_be_female'}]); }); +// $(jqButtons.get(1)).on('click', function () { handler.processServerHints([{id:'popup_unknown', start: 20, end: 26}]); }); +// $(jqButtons.get(2)).on('click', function () { handler.processServerHints([{id:'drop_down', start: 20, end: 26, choices:['ena', 'dva', 'tri']}]); }); + + $('#btn_code_hint').on('click', function () { + if (language === 'prolog') { + terminal.append('hint.\n', 'input'); + terminal.inputDisable(); + } + var doc = editor.getDoc(); + codeq.comms.sendHint({ + 'language': 'prolog', + 'program': editor.getDoc().getValue(), + 'problem_id': problem.id + }).then( + function hintSuccess(data) { + if (data.code === 0) + handler.processServerHints(data.hints); + else + terminal.append(data.message + '\n', 'error'); + }, + function hintFailed (error) { + terminal.append(error + '\n', 'error'); + } + ).fin(function () { + if(language === 'prolog'){ + terminal.inputEnable(); + terminal.append('?- ', 'output'); + } + } + ).done(); + }); + $('#btn_code_test').on('click', function () { + if(language === 'prolog'){ + terminal.append('test.\n', 'input'); + terminal.inputDisable(); + } + var doc = editor.getDoc(); + codeq.comms.sendTest({ + 'language': language, + 'program': editor.getDoc().getValue(), + 'problem_id': problem.id + }).then( + function testSuccess(data) { + if (data.code === 0) + handler.processServerHints(data.hints); + else + terminal.append(data.message + '\n', 'error'); + }, + function testFailed (error) { + terminal.append(error + '\n', 'error'); + } + ).fin(function () { + if(language === 'prolog'){ + terminal.inputEnable(); + terminal.append('?- ', 'output'); + } + } + ).done(); + }); + + if(language === 'python'){ + // TODO first line of interpreter output is buffered without this, why? + codeq.comms.sendPush({ + 'text': '' + }); + } + + return handler; +}; diff --git a/js/codeq/python.js b/js/codeq/python.js new file mode 100644 index 0000000..868055c --- /dev/null +++ b/js/codeq/python.js @@ -0,0 +1,393 @@ +/** + * Created by robert on 9/17/15. + * + * The python state of the state machine. When it is entered it'll prepare the console and code editor and load a sub-state machine which represents the 4 different parts fo the screen. + * + * Currentyl it is mostly a copy of the prolog state (the exception is of course the makePythonTerminalHandler instead of makePrologTerminalHandler) + */ + +(function() { + var subScreens,//this will the actual (sub)state machine + stateNameTag = 'stateName';//a tag for data which is added to some html elements + + //get the divs which are the main elements being change on transitions between states in the sub-state machine + var divs = {}; + divs['description'] = $('#description_outer_div'); + divs['code'] = $('#code_editor_outer_div'); + divs['console'] = $('#console_outer_div'); + divs['info'] = $('#info_outer_div'); + + //these tags connect the div to their respective state in the sub-state machine + divs['description'].data(stateNameTag, 'description'); + divs['code'].data(stateNameTag, 'code'); + divs['console'].data(stateNameTag, 'console'); + divs['info'].data(stateNameTag, 'info'); + + var transitionEventName = 'mousedown',//event name of the event which will trigger the transition between these substates - the most common transition at least (there are some corner cases on the hint and test buttons -> see the code below) + /** + * the general function which should be called on the mousedown event to trigger the transition between states + */ + mouseDownEventFunction = function () { + subScreens.transition($(this).data(stateNameTag)); + }, + /** + * removes the above function from the divs + */ + removeListenersFromDivs = function () { + $.each(divs, function (i, value) { + value.off(transitionEventName, mouseDownEventFunction); + }); + }, + /** + * removes the 'block' class from all divs we need to change when we change the current state + */ + removeBlockClassesFromDivs = function () { + $.each(divs, function (i, value) { + value.removeClass('block'); + }); + }, + /** + * removes all those special classes from the divs, which are set when a new state is entered and it also adds the 'block' class back + */ + removeChangedClassesFromDivs = function () { + $.each(divs, function (i, value) { + value.removeClass('block-focus block-less-width block-less-height block-less-everything').addClass('block');//these class names were chosen for the view where the screen is partitioned in 4 quarters (2 by 2) + }); + }, + /** + * + * @param current the key of the state ('description','code','hint','console'), which is currently active, because it must not have a transition set + */ + setTransitionsBetweenDivs = function (current) { + $.each(divs, function (key, value) { + if (current !== key) value.on(transitionEventName, mouseDownEventFunction); + }); + }, + /** + * + * @param divsWithClasses a object where we have the keys of the divs object paired with the class name we want to assign to them e.g. : + * { + 'description': 'block-focus', + 'code': 'block-less-width', + 'console': 'block-less-height', + 'info': 'block-less-everything' + } + */ + addClassesToDivs = function (divsWithClasses) { + $.each(divsWithClasses, function (divName, className) { + divs[divName].addClass(className); + }); + }, + /** + * the buttons (hint and test) will have to 'eat' the mousedown event to prevent some weird transitions. + * Because a click on them won't trigger the transition to the code part, but will transition to the hints part. + * + * @param event + */ + mouseDownEventIgnoreFun = function(event){ + event.stopPropagation(); + }, + /** + * The transition from the buttons to the hints screen will be triggered with this function + */ + clickListenerTransitionFun = function(){ + subScreens.transition(divs['info'].data(stateNameTag)); + }, + /** + * add the above function to the buttons + */ + addClickListenerTranstions = function(){ + $('#btn_code_hint').on('click',clickListenerTransitionFun); + $('#btn_code_test').on('click',clickListenerTransitionFun); + + $('#btn_code_hint').on(transitionEventName,mouseDownEventIgnoreFun); + $('#btn_code_test').on(transitionEventName,mouseDownEventIgnoreFun); + }, + /** + * and a function to remove it + */ + removeClickListenerTransition = function(){ + $('#btn_code_hint').off('click',clickListenerTransitionFun); + $('#btn_code_test').off('click',clickListenerTransitionFun); + + $('#btn_code_hint').off(transitionEventName,mouseDownEventIgnoreFun); + $('#btn_code_test').off(transitionEventName,mouseDownEventIgnoreFun); + }, + substates = { + 'description': { + 'enter': function () { + removeBlockClassesFromDivs(); + addClassesToDivs({ + 'description': 'block-focus', + 'code': 'block-less-width', + 'console': 'block-less-height', + 'info': 'block-less-everything' + }); + setTransitionsBetweenDivs('description'); + + //and set up the buttons + addClickListenerTranstions(); + }, + 'exit': function () { + removeChangedClassesFromDivs(); + removeListenersFromDivs(); + removeClickListenerTransition(); + } + }, + 'code': { + 'enter': function () { + removeBlockClassesFromDivs(); + addClassesToDivs({ + 'description': 'block-less-width', + 'code': 'block-focus', + 'console': 'block-less-everything', + 'info': 'block-less-height' + }); + setTransitionsBetweenDivs('code'); + + //and set up the buttons + addClickListenerTranstions(); + }, + 'exit': function () { + removeChangedClassesFromDivs(); + removeListenersFromDivs(); + removeClickListenerTransition(); + } + }, + 'info': { + 'enter': function () { + removeBlockClassesFromDivs(); + addClassesToDivs({ + 'description': 'block-less-everything', + 'code': 'block-less-height', + 'console': 'block-less-width', + 'info': 'block-focus' + }); + setTransitionsBetweenDivs('info'); + + //and set up the buttons + addClickListenerTranstions(); + }, + 'exit': function () { + removeChangedClassesFromDivs(); + removeListenersFromDivs(); + removeClickListenerTransition(); + } + }, + 'console': { + 'enter': function () { + removeBlockClassesFromDivs(); + addClassesToDivs({ + 'description': 'block-less-height', + 'code': 'block-less-everything', + 'console': 'block-focus', + 'info': 'block-less-width' + }); + setTransitionsBetweenDivs('console'); + + //and set up the buttons + addClickListenerTranstions(); + }, + 'exit': function () { + removeChangedClassesFromDivs(); + removeListenersFromDivs(); + removeClickListenerTransition(); + } + } + }; + var pythonHandler; + codeq.globalStateMachine.register('python', { + 'enter': function (data) { + $('#disabled').css('display', ''); + $('#screen_prolog').css('display', '');//we have to show the screen now so the code editor shopws its initial values correctly + pythonHandler = createPythonHandler(data.data);//codeq.createProgrammingLanguageHandler('prolog',data.data,codeq.makePythonTerminalHandler); + subScreens = codeq.makeStateMachine(substates); + subScreens.transition(divs['description'].data(stateNameTag)); + Q.delay(100).then(function(){ + $('div.col-lg-3.col-md-6.col-sm-12').addClass('transition'); + }).done(); + $('#disabled').css('display', 'none'); + }, + 'exit': function () { + $('#screen_prolog').css('display', 'none'); + $('div.col-lg-3.col-md-6.col-sm-12').removeClass('transition'); + pythonHandler.destroy(); + subScreens.destroy(); + } + }); + + // a constant + var firstCharacterPos = {'line': 0, 'ch': 0}; + + var makePythonTerminalHandler = function (jqConsole, editor, problem_id, activityHandler) { + var terminal = codeq.makeConsole(jqConsole, { + 'greeting': 'CodeQ Python terminal proxy' + }), + tcs = function terminalCommandSuccess (data) { + if (data.code !== 0) { + terminal.append(data.message, 'error'); + } + }, + tcf = function terminalCommandFailed (error) { + terminal.append(error + '\n', 'error'); + }; + + terminal.onInput = function (text) { + return codeq.comms.sendPush({ + 'text': text + '\n' + }).then(tcs, tcf); + }; + + codeq.comms.on('terminal_output', function (data) { + var text = data.text; + terminal.append(text, 'output'); + lines = text.split('\n'); + terminal.leftmostCol = lines[lines.length-1].length; + }); + + terminal.leftmostCol = 1; + + return terminal; + }; + + var makeActivityHandler = function (editor, problem_id) { + var lastActivityMillis = Date.now(), + deltaActivityMillis = function deltaActivityMillisFunc () { + var now = Date.now(), + dt = now - lastActivityMillis; + lastActivityMillis = now; + return dt; + }, + queue = [], + ts = null, + timer = function () { + var promise; + ts = null; + if (queue.length === 0) return Q(true); + promise = codeq.comms.sendActivity(queue, editor.getDoc().getValue(), problem_id); + queue.length = 0; + return promise; + }, + flush = function () { + clearTimeout(ts); + return timer(); + }; + + return { + 'queueTrace': function (trace) { + trace['dt'] = deltaActivityMillis(); + queue.push(trace); + if (ts === null) ts = setTimeout(timer, 10000); // flush every 10 seconds + return this; + }, + 'flush': flush, + 'addAndPurge': function (trace) { + var accumulatedTrace = queue; + queue = []; + trace['dt'] = deltaActivityMillis(); + accumulatedTrace.push(trace); + if (ts !== null) { + clearTimeout(ts); + ts = null; + } + return accumulatedTrace; + } + }; + }; + + + + /** + * Creates a new handler for the given Prolog assignment definition. + * + * @param {PrologTaskDef} info + * @returns {{destroy: Function, processServerHints: Function}} + */ + var createPythonHandler = function (info) { + var problem = info.problem, + jqDescription = $('#description'), + jqEditor = $('#code_editor'), + jqConsole = $('#console'), + jqHints = $('#info'), + editor = CodeMirror(jqEditor[0], { cursorHeight: 0.85, lineNumbers: true, matchBrackets: true, mode: 'python' }), + activityHandler = makeActivityHandler(editor, problem.id), + terminal = makePythonTerminalHandler(jqConsole, editor, problem.id, activityHandler), + hinter = codeq.makeHinter(jqHints, jqEditor, editor, problem.hint); + + editor.setValue(info.solution); + $('#title').text(problem.slug); + jqDescription.html(problem.description); + + editor.on('change', function (instance, changeObj) { + var doc = editor.getDoc(), + pos = codeq.codePointCount(doc.getRange(firstCharacterPos, changeObj.from)); + + if (changeObj.removed) { + activityHandler.queueTrace({'typ': 'rm', 'off': pos, 'len': codeq.codePointCount(changeObj.removed)}); + } + + if (changeObj.text) { + activityHandler.queueTrace({'typ': 'ins', 'off': pos, 'txt': changeObj.text}); + } + }); + + $('#btn_code_hint').on('click', function () { + var doc = editor.getDoc(); + codeq.comms.sendHint({ + 'language': 'python', + 'program': editor.getDoc().getValue(), + 'problem_id': problem.id + }).then( + function hintSuccess(data) { + if (data.code === 0) + hinter.handle(data.hints); + else + terminal.append('error: ' + data.message); + }, + function hintFailed (error) { + terminal.append('exception: ' + error); + } + ).done(); + }); + $('#btn_code_test').on('click', function () { + var doc = editor.getDoc(); + codeq.comms.sendTest({ + 'language': 'python', + 'program': editor.getDoc().getValue(), + 'problem_id': problem.id + }).then( + function testSuccess(data) { + if (data.code === 0) + hinter.handle(data.hints); + else + terminal.append('error: ' + data.message); + }, + function testFailed (error) { + terminal.append('exception: ' + error); + } + ).done(); + }); + + // TODO first line of interpreter output is buffered without this, why? + codeq.comms.sendPush({ + 'text': '' + }); + + return { + destroy: function () { + $('#btn_code_hint').off('click'); + $('#btn_code_test').off('click'); + editor.off('change'); + hinter.destroy(); + terminal.destroy(); + jqDescription.empty(); + jqEditor.empty(); // TODO: perhaps you do not want to "free" the editor, just empty it + jqConsole.empty(); // TODO: the same with the console + jqDescription = null; + jqEditor = null; + jqConsole = null; + jqHints = null; + } + }; + }; +})(); diff --git a/js/codeq/startup.js b/js/codeq/startup.js index 730a9f8..ccef9f9 100644 --- a/js/codeq/startup.js +++ b/js/codeq/startup.js @@ -1,5 +1,5 @@ $(document).ready(function () { - codeq.comms.connect().then(function () { + /*codeq.comms.connect().then(function () { return codeq.comms.send({'action': 'list_problems'}); }).then( function success(data) { @@ -94,5 +94,7 @@ $(document).ready(function () { $('#disabled').css('cursor', ''); alert('Request to obtain list of problems failed: ' + reason); } - ).done(); + ).done();*/ + + codeq.globalStateMachine.transition('login'); });
\ No newline at end of file diff --git a/js/codeq/stateMachine.js b/js/codeq/stateMachine.js new file mode 100644 index 0000000..cfb27e7 --- /dev/null +++ b/js/codeq/stateMachine.js @@ -0,0 +1,22 @@ +/** + * Created by robert on 9/15/15. + */ + +codeq.makeStateMachine = function(def){ + var currState = null; + return { + 'transition': function(name){ + if(currState !== null) currState.exit(); + currState = def[name]; + currState.enter.apply(currState,Array.prototype.slice.apply(arguments,[1])); + }, + 'destroy': function(){ + if(currState !== null) currState.exit(); + currState = null; + }, + 'register': function(name,state){ + def[name] = state; + } + } +}; +codeq.globalStateMachine = codeq.makeStateMachine({});
\ No newline at end of file diff --git a/js/prolog.js b/js/prolog.js index fad1dd4..29ceb3d 100644 --- a/js/prolog.js +++ b/js/prolog.js @@ -8,7 +8,6 @@ var makePrologTerminalHandler = function (jqConsole, editor, problem_id, activityHandler) { var promptMode = true, // default: query composition; alternative: query result browsing - manualStop = false, // if the user stopped showing next answers (false) or if there are no more answers (true) terminal = codeq.makeConsole(jqConsole, { 'greeting': 'CodeQ Prolog terminal proxy' }), @@ -25,7 +24,7 @@ } if (promptMode) { terminal.setLineBuffered(); - terminal.append(manualStop ? '?- ' : '.\n?- ', 'output'); + terminal.append('.\n?- ', 'output'); } }, tcf = function terminalCommandFailed (error) { @@ -50,7 +49,6 @@ terminal.onInput = function (command) { if (promptMode) { promptMode = false; - manualStop = false; terminal.setNotBuffered(); return codeq.comms.sendQuery({ 'problem_id': problem_id, @@ -72,7 +70,6 @@ } else { // stop searching for answers - manualStop = true; return codeq.comms.sendQuery({ 'problem_id': problem_id, 'step': 'end', diff --git a/js/python.js b/js/python.js index 1f2b5cb..fa4943b 100644 --- a/js/python.js +++ b/js/python.js @@ -6,6 +6,7 @@ // a constant var firstCharacterPos = {'line': 0, 'ch': 0}; + //codeq.makePythonTerminalHandler = function (jqConsole, editor, problem_id, activityHandler) { var makePythonTerminalHandler = function (jqConsole, editor, problem_id, activityHandler) { var terminal = codeq.makeConsole(jqConsole, { 'greeting': 'CodeQ Python terminal proxy' @@ -37,7 +38,7 @@ return terminal; }; - var makeActivityHandler = function (editor, problem_id) { + var makeActivityHandler = function (editor, problem_id) { var lastActivityMillis = Date.now(), deltaActivityMillis = function deltaActivityMillisFunc () { var now = Date.now(), |