def send_keyword(self, end_word): """We send a keyword when the tag 'KEYWORD' is in the tags of end_word. end_word : the character of the last word This function is called when self.typed_char is True and: -a user types a non alphanum character (keypress_event). end_word is the character of the last word. -a user types a newline (newline_and_indent_event). end_word is the character of the previous instruction's last character. -the cursor has been moved (prev_move_cursor_event and move_cursor_event) end_word is the character of the previous cursor position. We don't simply check all keypresses because we want to prevent cases where the user types something like 'define' or 'assert'. In these cases, the user typed the keywords 'def' or 'as' but we don't want to trace these. self.typed_char is used to prevent sending the same keyword multiple times and to only trace the keywords the user effectively typed. """ if end_word == "1.0": # Bug with select all return tags = self.tag_names(end_word) if 'KEYWORD' in self.tag_names(end_word + "-1c"): index1, index2 = self.tag_prevrange('KEYWORD', end_word) keyword = self.get(index1, index2) line_number = self.get_current_line_number() extensions = { "https://www.lip6.fr/mocah/invalidURI/extensions/keyword-typed": keyword, "https://www.lip6.fr/mocah/invalidURI/extensions/filename": self.short_title() } self.add_context(extensions, line_number) tracing.send_statement("typed", "keyword", extensions)
def send_update_changed_line(self, newline=False, force_sending=False): """Send a statement whenever a user modified an instruction. force_sending is used to force the sending (program execution, backspace over previous line)""" if self.old_line is None: return old_line = self.old_line new_line = self.create_line(old_line.line_number) cursor_line_number = self.get_current_line_number() cursor_changed_line = old_line.line_number != cursor_line_number # not_both_empty: Check if whether old_line, new_line or both are not empty spaces not_both_empty = (old_line.instruction != "" and not old_line.instruction.isspace()) not_both_empty = not_both_empty or (new_line.instruction != "" and not new_line.instruction.isspace()) instruction_has_been_modified = old_line.instruction.strip( ) != new_line.instruction.strip() if cursor_changed_line or force_sending: if not_both_empty and instruction_has_been_modified: if new_line.line_number == 1: tracing.check_modified_student_number(new_line.instruction) extensions = self.create_changed_line_extensions( old_line, new_line) self.add_context(extensions, old_line.line_number) # print(extensions['https://www.lip6.fr/mocah/invalidURI/extensions/old-instruction']) # print(extensions['https://www.lip6.fr/mocah/invalidURI/extensions/new-instruction']) tracing.send_statement("modified", "instruction", extensions) if newline: # If newline, we want old_line to be considered empty self.old_line = Line(cursor_line_number, "", "empty") else: self.old_line = self.create_line(cursor_line_number)
def save_as(self, event): tracing.user_is_interacting() if self.filename: old_filename = self.filename else: old_filename = "Sans nom" filename = self.asksavefile() if filename: if self.writefile(filename): #self.opened=TRUE self.set_filename(filename) self.set_saved(1) try: self.editwin.store_file_breaks() except AttributeError: pass tracing.send_statement( "saved-as", "file", { "https://www.lip6.fr/mocah/invalidURI/extensions/old-filename": os.path.basename(old_filename), "https://www.lip6.fr/mocah/invalidURI/extensions/new-filename": os.path.basename(filename) }) self.editwin.focus_set() self.updaterecentfileslist(filename) return "break"
def new_file(self, event=None): """ Creates a new empty editor and put it into the pyEditorList """ tracing.user_is_interacting() file_editor = PyEditorFrame(self.editor_list) self.editor_list.add(file_editor, self.main_view.editor_widget, text=file_editor.get_file_name()) tracing.send_statement("created", "file")
def copy_event(self, event): # Trace copy event in the output console first = self.index("sel.first") last = self.index("sel.last") if first != "" and last != "": tracing.send_statement( "copied", "output-console", { "https://www.lip6.fr/mocah/invalidURI/extensions/text": self.get(first, last) })
def paste_event(self, event): """User pasted something""" pasted_text = self.clipboard_get() insert_index = self.index("insert") if pasted_text != "": extensions = { "https://www.lip6.fr/mocah/invalidURI/extensions/text": pasted_text, "https://www.lip6.fr/mocah/invalidURI/extensions/index": insert_index } self.add_context(extensions, int(insert_index.split('.')[0])) tracing.send_statement("inserted", "text", extensions)
def send_deletion(self, first, last): if self.is_multi_line_deletion(first, last): self.reset_line() extensions = { "https://www.lip6.fr/mocah/invalidURI/extensions/text": self.get(first, last), "https://www.lip6.fr/mocah/invalidURI/extensions/first-index": first, "https://www.lip6.fr/mocah/invalidURI/extensions/last-index": last } self.add_context(extensions, int(first.split('.')[0])) tracing.send_statement("deleted", "text", extensions)
def copied_event(self, event): """User copied something""" first, last = self.get_selection_indices() if first and last: extensions = { "https://www.lip6.fr/mocah/invalidURI/extensions/text": self.get(first, last), "https://www.lip6.fr/mocah/invalidURI/extensions/first-index": first, "https://www.lip6.fr/mocah/invalidURI/extensions/last-index": last } self.add_context(extensions, int(first.split('.')[0])) tracing.send_statement("copied", "text", extensions)
def evaluate_action(self, *args): """ Evaluate the expression in the input console """ expr = self.input_console.get() if not expr: return tracing.send_statement("started", "evaluation", { "https://www.lip6.fr/mocah/invalidURI/extensions/mode": tr(self.mode) }) tracing.user_is_interacting() local_interpreter = False if self.interpreter is None: self.interpreter = InterpreterProxy(self.app.root, self.app.mode, "<<console>>") local_interpreter = True self.app.running_interpreter_proxy = self.interpreter callback_called = False # the call back def callback(ok, report): nonlocal callback_called if callback_called: return else: callback_called = True if ok: self.input_history.record(expr) self.input_console.delete(0, END) self.write_report(ok, report, 'eval') if local_interpreter: self.interpreter.kill() self.interpreter = None self.app.running_interpreter_proxy = None self.app.icon_widget.disable_icon_running() self.app.running_interpreter_callback = None tracing.send_statement_evaluate(report, tr(self.mode), instruction=expr) # non-blocking call self.app.icon_widget.enable_icon_running() self.app.running_interpreter_callback = callback self.interpreter.run_evaluation(expr, callback)
def change_mode(self, event=None): """ Swap the python mode : full python or student """ if self.mode == "student": self.mode = "full" else: self.mode = "student" self.icon_widget.switch_icon_mode(self.mode) self.console.change_mode(tr(self.mode)) self.status_bar.change_mode(tr(self.mode)) if event: tracing.user_is_interacting() tracing.send_statement( "switched", "mode", { "https://www.lip6.fr/mocah/invalidURI/extensions/mode": tr(self.mode) })
def redo_event(self, event): if self.pointer >= len(self.undolist): self.bell() return "break" cmd = self.undolist[self.pointer] if self.text is not None: filename = self.text.short_title() tracing.send_statement("redid", "sequence", {"https://www.lip6.fr/mocah/invalidURI/extensions/sequence-list": str(cmd), "https://www.lip6.fr/mocah/invalidURI/extensions/filename": filename}) self.text.reset_line() cmd.redo(self.delegate) self.pointer = self.pointer + 1 self.can_merge = False self.check_saved() return "break"
def on_close_release(self, event): """Called when the button is released over the close button""" # Code for tracing changed tabs self.new_tab = self.select() if not self.instate([ 'pressed' ]) and self.old_tab != self.new_tab and self.old_tab != "": old_tab_filename = self.get_filename(self.old_tab) new_tab_filename = self.get_filename(self.new_tab) tracing.send_statement( "switched", "file", { "https://www.lip6.fr/mocah/invalidURI/extensions/old-tab": old_tab_filename, "https://www.lip6.fr/mocah/invalidURI/extensions/current-tab": new_tab_filename }) # Code for closing tab if not self.instate(['pressed']): return element = self.identify(event.x, event.y) try: index = self.index("@%d,%d" % (event.x, event.y)) except tk.TclError: return if "close" in element and self._active == index: #do the proper linking to the event old_tab_filename = self.get_filename(self.old_tab) self.close_current_editor() self.new_tab = self.select() if self.new_tab != "": new_tab_filename = self.get_filename(self.new_tab) else: new_tab_filename = "no tab selected" tracing.send_statement( "closed", "file", { "https://www.lip6.fr/mocah/invalidURI/extensions/closed-tab": old_tab_filename, "https://www.lip6.fr/mocah/invalidURI/extensions/current-tab": new_tab_filename }) self.event_generate("<<NotebookTabClosed>>") self.state(["!pressed"]) self._active = None
def check_user_state(self): """ Send a statement of the state of the user. User has 3 state: idle, interacting or typing """ last_typing_time, last_interacting_time = tracing.get_action_timestamps( ) elapsed_typing_time = time.time( ) - last_typing_time if last_typing_time else None elapsed_interacting_time = time.time( ) - last_interacting_time if last_interacting_time else None inactivity_threshhold = 30 new_state = "" if elapsed_typing_time is not None and elapsed_interacting_time is not None: if self.state == "idle": if elapsed_typing_time < inactivity_threshhold: new_state = "typing" elif elapsed_interacting_time < inactivity_threshhold: new_state = "interacting" elif self.state == "interacting": if elapsed_typing_time < inactivity_threshhold: new_state = "typing" elif elapsed_interacting_time > inactivity_threshhold: new_state = "idle" elif self.state == "typing" and elapsed_typing_time > inactivity_threshhold: if elapsed_interacting_time < 30: new_state = "interacting" elif elapsed_typing_time is not None: if self.state == "idle" and elapsed_typing_time < inactivity_threshhold: new_state = "typing" elif self.state == "typing" and elapsed_typing_time > inactivity_threshhold: new_state = "idle" elif elapsed_interacting_time is not None: if self.state == "idle" and elapsed_interacting_time < inactivity_threshhold: new_state = "interacting" elif self.state == "interacting" and elapsed_interacting_time > inactivity_threshhold: new_state = "idle" if new_state: tracing.send_statement("entered", new_state + "-state") self.state = new_state self.root.after(1000, self.check_user_state)
def save(self, event): tracing.user_is_interacting() if not self.filename: self.save_as(event) else: if self.writefile(self.filename): tracing.send_statement( "saved", "file", { "https://www.lip6.fr/mocah/invalidURI/extensions/filename": os.path.basename(self.filename) }) self.set_saved(True) try: self.editwin.store_file_breaks() except AttributeError: # may be a PyShell pass self.editwin.focus_set() if not self.filename: return None else: return self.filename
def run_module(self, event=None): """ Run the code : give the file name and code will be run from the source file """ tracing.user_is_interacting() # already running if self.running_interpreter_callback: if self.running_interpreter_proxy and self.running_interpreter_proxy.process.is_alive( ): report = RunReport() report.set_header("\n====== STOP ======\n") report.add_execution_error('error', tr('User interruption'), class_name='UserTerminatedError') report.set_footer("\n==================\n") self.running_interpreter_callback(False, report) self.running_interpreter_callback = None return # not (yet) running if self.editor_list.get_size() == 0: self.main_view.console.no_file_to_run_message() return reply = self.editor_list.get_current_editor().maybesave_run() if (reply != "cancel"): self.editor_list.get_current_editor().send_update_changed_line( force_sending=True) tracing.send_statement( "started", "execution", { "https://www.lip6.fr/mocah/invalidURI/extensions/mode": tr(self.mode) }) tracing.save_execution_start() file_name = self.editor_list.get_current_editor().long_title() self.update_title() self.status_bar.update_save_label(file_name) self.console.run(file_name)
def open(self, event=None): """ Open a file in the text editor """ tracing.user_is_interacting() file_editor = PyEditorFrame(self.editor_list, open=True) if (self.editor_list.focusOn(file_editor.long_title()) == False): if (file_editor.isOpen()): self.editor_list.add(file_editor, self.main_view.editor_widget, text=file_editor.get_file_name()) extensions = { "https://www.lip6.fr/mocah/invalidURI/extensions/filename": file_editor.get_file_name() } with open(file_editor.long_title(), "r") as source: text = source.read() extensions[ "https://www.lip6.fr/mocah/invalidURI/extensions/text"] = text extensions[ "https://www.lip6.fr/mocah/invalidURI/extensions/filelength"] = len( text) tracing.send_statement("opened", "file", extensions) #not clean, io should be handled here and should not require creation of PyEditor widget else: file_editor.destroy()