class Editor(ttk.Frame): def __init__(self, master, filename=None): ttk.Frame.__init__(self, master) assert isinstance(master, EditorNotebook) # parent of codeview will be workbench so that it can be maximized self._code_view = CodeView(get_workbench(), propose_remove_line_numbers=True, font=get_workbench().get_font("EditorFont")) get_workbench().event_generate("EditorTextCreated", editor=self, text_widget=self.get_text_widget()) self._code_view.grid(row=0, column=0, sticky=tk.NSEW, in_=self) self._code_view.home_widget = self # don't forget home self.maximizable_widget = self._code_view self.columnconfigure(0, weight=1) self.rowconfigure(0, weight=1) self._filename = None if filename is not None: self._load_file(filename) self._code_view.text.edit_modified(False) self._code_view.text.bind("<<Modified>>", self._on_text_modified, True) self._code_view.text.bind("<<TextChange>>", self._on_text_change, True) self._code_view.text.bind("<Control-Tab>", self._control_tab, True) get_workbench().bind("AfterKnownMagicCommand", self._listen_for_execute, True) get_workbench().bind("ToplevelResult", self._listen_for_toplevel_result, True) self.update_appearance() def get_text_widget(self): return self._code_view.text def get_code_view(self): # TODO: try to get rid of this return self._code_view def get_filename(self, try_hard=False): if self._filename is None and try_hard: self.save_file() return self._filename def get_long_description(self): if self._filename is None: result = "<untitled>" else: result = self._filename try: index = self._code_view.text.index("insert") if index and "." in index: line, col = index.split(".") result += " @ {} : {}".format(line, int(col) + 1) except: exception("Finding cursor location") return result def _load_file(self, filename): with tokenize.open(filename) as fp: # TODO: support also text files source = fp.read() self._filename = filename get_workbench().event_generate("Open", editor=self, filename=filename) self._code_view.set_content(source) self._code_view.focus_set() self.master.remember_recent_file(filename) def is_modified(self): return self._code_view.text.edit_modified() def save_file_enabled(self): return self.is_modified() or not self.get_filename() def save_file(self, ask_filename=False): if self._filename is not None and not ask_filename: filename = self._filename get_workbench().event_generate("Save", editor=self, filename=filename) else: # http://tkinter.unpythonic.net/wiki/tkFileDialog filename = asksaveasfilename( filetypes=_dialog_filetypes, defaultextension=".py", initialdir=get_workbench().get_option("run.working_directory")) if filename in [ "", (), None ]: # Different tkinter versions may return different values return None # Seems that in some Python versions defaultextension # acts funny if filename.lower().endswith(".py.py"): filename = filename[:-3] get_workbench().event_generate("SaveAs", editor=self, filename=filename) content = self._code_view.get_content() encoding = "UTF-8" # TODO: check for marker in the head of the code try: f = open( filename, mode="wb", ) f.write(content.encode(encoding)) f.close() except PermissionError: if askyesno( "Permission Error", "Looks like this file or folder is not writable.\n\n" + "Do you want to save under another folder and/or filename?" ): return self.save_file(True) else: return None self._filename = filename self.master.remember_recent_file(filename) self._code_view.text.edit_modified(False) return self._filename def show(self): self.master.select(self) def update_appearance(self): self._code_view.set_line_numbers( get_workbench().get_option("view.show_line_numbers")) self._code_view.set_line_length_margin( get_workbench().get_option("view.recommended_line_length")) self._code_view.text.event_generate("<<UpdateAppearance>>") def _listen_for_execute(self, event): command, args = parse_shell_command(event.cmd_line) # Go read-only if command.lower() == "debug": if len(args) == 0: return filename = args[0] self_filename = self.get_filename() if self_filename is not None and os.path.basename( self_filename) == filename: # Not that command has only basename # so this solution may make more editors read-only than necessary self._code_view.text.set_read_only(True) def _listen_for_toplevel_result(self, event): self._code_view.text.set_read_only(False) def _control_tab(self, event): if event.state & 1: # shift was pressed direction = -1 else: direction = 1 self.master.select_next_prev_editor(direction) return "break" def _shift_control_tab(self, event): self.master.select_next_prev_editor(-1) return "break" def select_range(self, text_range): self._code_view.select_range(text_range) def focus_set(self): self._code_view.focus_set() def is_focused(self): return self.focus_displayof() == self._code_view.text def _on_text_modified(self, event): self.master.update_editor_title(self) def _on_text_change(self, event): self.master.update_editor_title(self) runner = get_runner() if (runner.get_state() in [ "running", "waiting_input", "waiting_debugger_command" ] and isinstance(runner.get_current_command(), (ToplevelCommand, DebuggerCommand))): # exclude running InlineCommands runner.interrupt_backend() def destroy(self): get_workbench().unbind("AfterKnownMagicCommand", self._listen_for_execute) get_workbench().unbind("ToplevelResult", self._listen_for_toplevel_result) ttk.Frame.destroy(self)
class Editor(ttk.Frame): def __init__(self, master, filename=None): ttk.Frame.__init__(self, master) assert isinstance(master, EditorNotebook) # parent of codeview will be workbench so that it can be maximized self._code_view = CodeView(get_workbench(), propose_remove_line_numbers=True, font=get_workbench().get_font("EditorFont")) get_workbench().event_generate("EditorTextCreated", editor=self, text_widget=self.get_text_widget()) self._code_view.grid(row=0, column=0, sticky=tk.NSEW, in_=self) self._code_view.home_widget = self # don't forget home self.maximizable_widget = self._code_view self.columnconfigure(0, weight=1) self.rowconfigure(0, weight=1) self._filename = None self.file_encoding = None if filename is not None: self._load_file(filename) self._code_view.text.edit_modified(False) self._code_view.text.bind("<<Modified>>", lambda e: master.update_editor_title(self), True) self._code_view.text.bind("<Control-Tab>", self._control_tab, True) get_workbench().bind("AfterKnownMagicCommand", self._listen_for_execute, True) get_workbench().bind("ToplevelResult", self._listen_for_toplevel_result, True) self.update_appearance() def get_text_widget(self): return self._code_view.text def get_code_view(self): # TODO: try to get rid of this return self._code_view def get_filename(self, try_hard=False): if self._filename is None and try_hard: self.save_file() return self._filename def _load_file(self, filename): source, self.file_encoding = misc_utils.read_python_file( filename) # TODO: support also text files self._filename = filename get_workbench().event_generate("Open", editor=self, filename=filename) self._code_view.set_content(source) self._code_view.focus_set() self.master.remember_recent_file(filename) def is_modified(self): return self._code_view.text.edit_modified() def save_file_enabled(self): return self.is_modified() or not self.get_filename() def save_file(self, ask_filename=False): if self._filename is not None and not ask_filename: filename = self._filename get_workbench().event_generate("Save", editor=self, filename=filename) else: # http://tkinter.unpythonic.net/wiki/tkFileDialog filename = asksaveasfilename( filetypes=_dialog_filetypes, defaultextension=".py", initialdir=get_workbench().get_option("run.working_directory")) if filename in [ "", (), None ]: # Different tkinter versions may return different values return None # Seems that in some Python versions defaultextension # acts funny if filename.lower().endswith(".py.py"): filename = filename[:-3] get_workbench().event_generate("SaveAs", editor=self, filename=filename) content = self._code_view.get_content() encoding = self.file_encoding or "UTF-8" f = open( filename, mode="wb", ) f.write(content.encode(encoding)) f.close() self._filename = filename self.master.remember_recent_file(filename) self._code_view.text.edit_modified(False) return self._filename def show(self): self.master.select(self) def update_appearance(self): self._code_view.set_line_numbers( get_workbench().get_option("view.show_line_numbers")) self._code_view.set_line_length_margin( get_workbench().get_option("view.recommended_line_length")) self._code_view.text.event_generate("<<UpdateAppearance>>") def _listen_for_execute(self, event): command, args = parse_shell_command(event.cmd_line) if command.lower() in ["run", "debug"]: if len(args) == 0: return filename = args[0] self_filename = self.get_filename() if self_filename is not None and os.path.basename( self_filename) == filename: # Not that command has only basename # so this solution may make more editors read-only than necessary self._code_view.text.set_read_only(True) def _listen_for_toplevel_result(self, event): self._code_view.text.set_read_only(False) def _control_tab(self, event): if event.state & 1: # shift was pressed direction = -1 else: direction = 1 self.master.select_next_prev_editor(direction) return "break" def _shift_control_tab(self, event): self.master.select_next_prev_editor(-1) return "break" def select_range(self, text_range): self._code_view.select_range(text_range) def focus_set(self): self._code_view.focus_set() def is_focused(self): return self.focus_displayof() == self._code_view.text def destroy(self): get_workbench().unbind("AfterKnownMagicCommand", self._listen_for_execute) get_workbench().unbind("ToplevelResult", self._listen_for_toplevel_result) ttk.Frame.destroy(self)