# CodeQ: an online programming tutor. # Copyright (C) 2015 UL FRI # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU Affero General Public License as published by the Free # Software Foundation, either version 3 of the License, or (at your option) any # later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more # details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . import curses import pickle import sys from monkey.action import parse, expand from db.models import Problem, Solution if len(sys.argv) > 1: if sys.argv[1].endswith('.pickle'): with open(sys.argv[1], 'rb') as f: attempts = pickle.load(f) else: pid = int(sys.argv[1]) attempts = sorted(Solution.filter(problem_id=pid), key=lambda s: s.codeq_user_id) else: # print all problem ids print('problems:') for problem in Problem.list(): print(' {}\t{}'.format(problem.id, problem.identifier)) print() pid = int(input('enter problem id: ')) attempts = sorted(Solution.filter(problem_id=pid), key=lambda s: s.codeq_user_id) def get_actions(trace): try: actions = parse(trace) expand(actions) return actions except Exception as ex: sys.stderr.write('Error parsing action log: {}\n'.format(ex)) return [] def main(stdscr): def draw_code(win, text, highlight_pos=None, highlight_color=0): win.erase() win.border() y = x = 0 for n, char in enumerate(text + ' '): attr = curses.color_pair(highlight_color if n == highlight_pos else 1) if char == '\t': char = ' ' if char != '\n': win.addstr(y+1, x+1, char, attr) x += 1 else: # add an empty cell at end of each line for highlighting win.addstr(y+1, x+1, ' ', attr) x = 0 y += 1 win.refresh() def draw_status(win, uid, action_idx): win.erase() text = 'usr {}, act {}/{}: {}'.format(uid, action_idx, len(actions), actions[min(len(actions)-1, index)]) win.addstr(0, 0, text[:79]) win.refresh() stdscr.clear() if curses.has_colors(): curses.init_pair(1, curses.COLOR_WHITE, 0) curses.init_pair(2, curses.COLOR_BLACK, curses.COLOR_GREEN) curses.init_pair(3, curses.COLOR_WHITE, curses.COLOR_RED) curses.init_color(0, 0, 0, 0) curses.curs_set(0) stdscr.attrset(curses.color_pair(1)) statuswin = stdscr.derwin(1, 80, 0, 0) codewin = stdscr.derwin(22, 80, 1, 0) attempt_idx = 0 if len(sys.argv) > 2: uid = int(sys.argv[2]) for attempt_idx, a in enumerate(attempts): if a.codeq_user_id == uid: break attempt = attempts[attempt_idx] actions = get_actions(attempt.trace) index = 0 code = '' draw_status(statuswin, attempt.codeq_user_id, index) draw_code(codewin, code, 0, 2) while True: c = stdscr.getch() if 0 < c < 256: c = chr(c) if c == curses.KEY_HOME: # go to first action index = 0 code = '' elif c == curses.KEY_LEFT: # go to previous action if index > 0: index -= 1 action = actions[index] code = action.unapply(code) elif c == curses.KEY_RIGHT: # go to next action if index < len(actions): action = actions[index] code = action.apply(code) index += 1 elif c == 'n': # go to next solution if attempt_idx < len(attempts)-1: attempt_idx += 1 attempt = attempts[attempt_idx] actions = get_actions(attempt.trace) code = '' index = 0 elif c == 'N': # go to previous solution if attempt_idx > 0: attempt_idx -= 1 attempt = attempts[attempt_idx] actions = get_actions(attempt.trace) code = '' index = 0 elif c == 'q': # quit break elif c == 't': # go to next 'test' action if index < len(actions) and actions[index].type == 'test': index += 1 while index < len(actions) and actions[index].type != 'test': code = actions[index].apply(code) index += 1 elif c == 'T': # go to previous 'test' action while index > 0 and actions[index-1].type != 'test': code = actions[index-1].unapply(code) index -= 1 if index > 0 and actions[index-1].type == 'test': index -= 1 # if active action is 'remove' or 'insert', highlight the character action = actions[index] if index < len(actions) else actions[-1] if action.type in {'insert', 'remove'}: highlight_pos = action.offset highlight_color = 2 if action.type == 'insert' else 3 else: highlight_pos = None highlight_color = 0 draw_status(statuswin, attempt.codeq_user_id, index) draw_code(codewin, code, highlight_pos, highlight_color) curses.wrapper(main)