def print_window(self, event): confirm = tkMessageBox.askokcancel(title="Print", message="Print to Default Printer", default=tkMessageBox.OK, parent=self.text) if not confirm: self.text.focus_set() return "break" tempfilename = None saved = self.get_saved() if saved: filename = self.filename # shell undo is reset after every prompt, looks saved, probably isn't if not saved or filename is None: (tfd, tempfilename) = tempfile.mkstemp(prefix='IDLE_tmp_') filename = tempfilename os.close(tfd) if not self.writefile(tempfilename): os.unlink(tempfilename) return "break" platform = os.name printPlatform = True if platform == 'posix': #posix platform command = idleConf.GetOption('main', 'General', 'print-command-posix') command = command + " 2>&1" elif platform == 'nt': #win32 platform command = idleConf.GetOption('main', 'General', 'print-command-win') else: #no printing for this platform printPlatform = False if printPlatform: #we can try to print for this platform command = command % pipes.quote(filename) pipe = os.popen(command, "r") # things can get ugly on NT if there is no printer available. output = pipe.read().strip() status = pipe.close() if status: output = "Printing failed (exit status 0x%x)\n" % \ status + output if output: output = "Printing command: %s\n" % repr(command) + output tkMessageBox.showerror("Print status", output, parent=self.text) else: #no printing for this platform message = "Printing is not enabled for this platform: %s" % platform tkMessageBox.showinfo("Print status", message, parent=self.text) if tempfilename: os.unlink(tempfilename) return "break"
def getfilename(self): """Get source filename. If not saved, offer to save (or create) file The debugger requires a source file. Make sure there is one, and that the current version of the source buffer has been saved. If the user declines to save or cancels the Save As dialog, return None. If the user has configured IDLE for Autosave, the file will be silently saved if it already exists and is dirty. """ filename = self.editwin.io.filename if not self.editwin.get_saved(): autosave = idleConf.GetOption('main', 'General', 'autosave', type='bool') if autosave and filename: self.editwin.io.save(None) else: confirm = self.ask_save_dialog() self.editwin.text.focus_set() if confirm: self.editwin.io.save(None) filename = self.editwin.io.filename else: filename = None return filename
def test_init(self): self.assertIs(self.history.text, self.text) self.assertEqual(self.history.history, []) self.assertIsNone(self.history.prefix) self.assertIsNone(self.history.pointer) self.assertEqual(self.history.cyclic, idleConf.GetOption("main", "History", "cyclic", 1, "bool"))
def __init__(self, pyshell): """Initialize data attributes and bind event methods. .text - Idle wrapper of tk Text widget, with .bell(). .history - source statements, possibly with multiple lines. .prefix - source already entered at prompt; filters history list. .pointer - index into history. .cyclic - wrap around history list (or not). @type pyshell: idlesporklib.PyShell.PyShell """ if 'SPORKPATH' not in os.environ.keys(): self.ph = Prehistory(os.path.expanduser('~')) else: self.ph = Prehistory(os.environ['SPORKPATH']) self.pyshell = pyshell self.text = text = pyshell.text self.history = self.ph.get() self.super_history = list(enumerate(self.history)) self.smart_history = [] self.prefix = None self.pointer = None self.suggested = [] self.cyclic = idleConf.GetOption("main", "History", "cyclic", 1, "bool") text.bind("<<history-previous>>", self.history_prev) text.bind("<<history-next>>", self.history_next) text.bind("<<history-guess>>", self.history_guess)
def __init__(self, parent, filename): "Configure tags and feed file to parser." uwide = idleConf.GetOption('main', 'EditorWindow', 'width', type='int') uhigh = idleConf.GetOption('main', 'EditorWindow', 'height', type='int') uhigh = 3 * uhigh // 4 # lines average 4/3 of editor line height Text.__init__(self, parent, wrap='word', highlightthickness=0, padx=5, borderwidth=0, width=uwide, height=uhigh) normalfont = self.findfont(['TkDefaultFont', 'arial', 'helvetica']) fixedfont = self.findfont(['TkFixedFont', 'monaco', 'courier']) self['font'] = (normalfont, 12) self.tag_configure('em', font=(normalfont, 12, 'italic')) self.tag_configure('h1', font=(normalfont, 20, 'bold')) self.tag_configure('h2', font=(normalfont, 18, 'bold')) self.tag_configure('h3', font=(normalfont, 15, 'bold')) self.tag_configure('pre', font=(fixedfont, 12), background='#f6f6ff') self.tag_configure('preblock', font=(fixedfont, 10), lmargin1=25, borderwidth=1, relief='solid', background='#eeffcc') self.tag_configure('l1', lmargin1=25, lmargin2=25) self.tag_configure('l2', lmargin1=50, lmargin2=50) self.tag_configure('l3', lmargin1=75, lmargin2=75) self.tag_configure('l4', lmargin1=100, lmargin2=100) self.parser = HelpParser(self) with open(filename) as f: contents = f.read().decode(encoding='utf-8') self.parser.feed(contents) self['state'] = 'disabled'
class __metaclass__(EnablableExtension.__metaclass__): _index_by_previous_line = idleConf.GetOption("extensions", "OutHist", "index_by_previous_line", type="bool", default=False, member_name='index_by_previous_line') just_changed = False @property def index_by_previous_line(cls): return cls._index_by_previous_line @index_by_previous_line.setter def index_by_previous_line(cls, value): cls._index_by_previous_line = value cls.set_index_by_previous_line(value)
def format_paragraph_event(self, event, limit=None): """Formats paragraph to a max width specified in idleConf. If text is selected, format_paragraph_event will start breaking lines at the max width, starting from the beginning selection. If no text is selected, format_paragraph_event uses the current cursor location to determine the paragraph (lines of text surrounded by blank lines) and formats it. The length limit parameter is for testing with a known value. """ if limit is None: # The default length limit is that defined by pep8 limit = idleConf.GetOption( 'extensions', 'FormatParagraph', 'max-width', type='int', default=72) text = self.editwin.text first, last = self.editwin.get_selection_indices() if first and last: data = text.get(first, last) comment_header = get_comment_header(data) else: first, last, comment_header, data = \ find_paragraph(text, text.index("insert")) if comment_header: newdata = reformat_comment(data, limit, comment_header) else: newdata = reformat_paragraph(data, limit) text.tag_remove("sel", "1.0", "end") if newdata != data: text.mark_set("insert", first) text.undo_block_start() text.delete(first, last) text.insert(first, newdata) text.undo_block_stop() else: text.mark_set("insert", last) text.see("insert") return "break"
def __init__(self, editwin): self.editwin = editwin self.text = editwin.text self.textfont = self.text["font"] self.label = None # self.info is a list of (line number, indent level, line text, block # keyword) tuples providing the block structure associated with # self.topvisible (the linenumber of the line displayed at the top of # the edit window). self.info[0] is initialized as a 'dummy' line which # starts the toplevel 'block' of the module. self.info = [(0, -1, "", False)] self.topvisible = 1 visible = idleConf.GetOption("extensions", "CodeContext", "visible", type="bool", default=False) if visible: self.toggle_code_context_event() self.editwin.setvar('<<toggle-code-context>>', True) # Start two update cycles, one for context lines, one for font changes. self.text.after(UPDATEINTERVAL, self.timer_event) self.text.after(FONTUPDATEINTERVAL, self.font_timer_event)
class ModuleCompletion(object): """ """ # Time in seconds after which to return TIMEOUT_GIVEUP = idleConf.GetOption("extensions", "AutoComplete", "imports-timeout", type="int", default=2) # Regular expression for the python import statement import_re = re.compile(r'(?P<name>[a-zA-Z_][a-zA-Z0-9_]*?)' r'(?P<package>[/\\]__init__)?' r'(?P<suffix>%s)$' % r'|'.join(re.escape(s) for s in _suffixes)) rootmodules_cache = {} allobjs = None executing_patch = False patched = False @staticmethod def module_list(path): """ Return the list containing the names of the modules available in the given folder. """ # sys.path has the cwd as an empty string, but isdir/listdir need it as '.' if path == '': path = '.' # A few local constants to be used in loops below pjoin = os.path.join if os.path.isdir(path): # Build a list of all files in the directory and all files # in its subdirectories. For performance reasons, do not # recurse more than one level into subdirectories. files = [] for root, dirs, nondirs in os.walk(path, followlinks=True): subdir = root[len(path) + 1:] if subdir: files.extend(pjoin(subdir, f) for f in nondirs) dirs[:] = [ ] # Do not recurse into additional subdirectories. else: files.extend(nondirs) else: try: files = list(zipimporter(path)._files.keys()) except: files = [] # Build a list of modules which match the import_re regex. modules = [] for f in files: m = ModuleCompletion.import_re.match(f) if m: modules.append(m.group('name')) return list(set(modules)) @staticmethod def get_root_modules(): """ Returns a list containing the names of all the modules available in the folders of the pythonpath. """ rootmodules_cache = ModuleCompletion.rootmodules_cache rootmodules = list(sys.builtin_module_names) start_time = time() store = False for path in sys.path: try: modules = rootmodules_cache[path] except KeyError: modules = ModuleCompletion.module_list(path) try: modules.remove('__init__') except ValueError: pass if path not in ('', '.'): # cwd modules should not be cached rootmodules_cache[path] = modules rootmodules.extend(modules) if time() - start_time > ModuleCompletion.TIMEOUT_GIVEUP: store = True break if store: ModuleCompletion.rootmodules_cache = rootmodules_cache rootmodules = list(set(rootmodules)) return rootmodules @staticmethod def is_importable(module, attr, only_modules): if only_modules: return inspect.ismodule(getattr(module, attr)) else: return not (attr[:2] == '__' and attr[-2:] == '__') @staticmethod def try_import(mod, only_modules=False): mod = mod.rstrip('.') try: m = __import__(mod) except: return [] mods = mod.split('.') for module in mods[1:]: m = getattr(m, module) m_is_init = hasattr(m, '__file__') and '__init__' in m.__file__ completions = [] if (not hasattr(m, '__file__')) or (not only_modules) or m_is_init: completions.extend([ attr for attr in dir(m) if ModuleCompletion.is_importable(m, attr, only_modules) ]) completions.extend(getattr(m, '__all__', [])) if m_is_init: completions.extend( ModuleCompletion.module_list(os.path.dirname(m.__file__))) completions = {c for c in completions if isinstance(c, (str, unicode))} completions.discard('__init__') return list(completions) @staticmethod def module_completion(line): """ Returns a list containing the completion possibilities for an import line. The line looks like this : 'import xml.d' 'from xml.dom import' """ words = line.split(' ') nwords = len(words) mods = [] lastword = '' # from whatever <tab> -> 'import ' if nwords >= 3 and words[-3] == 'from': return None # 'from xyz import abc<tab>' elif nwords >= 4 and words[-4] == 'from': mod = words[-3] mods = ModuleCompletion.try_import(mod) lastword = words[-1] # 'from xy<tab>' or 'import xy<tab>' elif nwords >= 2 and words[-2] in {'import', 'from'}: if words[-1] == '': mods = ModuleCompletion.get_root_modules() else: mod = words[-1].split('.') if len(mod) < 2: mods = ModuleCompletion.get_root_modules() else: mods = ModuleCompletion.try_import('.'.join(mod[:-1]), True) lastword = mod[-1] if mods: if lastword: withcase = [mod for mod in mods if mod.startswith(lastword)] if withcase: mods = withcase # if none, then case insensitive ones are ok too else: text_low = lastword.lower() mods = [ mod for mod in mods if mod.lower().startswith(text_low) ] mods = sorted(mods) if lastword.startswith('_'): return mods, mods else: return [mod for mod in mods if not mod.startswith('_')], mods
class CustomizePrompt(object): """ Extension to customize prompt line. Whatever you enter is given to `time.strftime`, with the following three new directives: %dM - minutes since last execution %dS - seconds since last execution %df - deci-seconds since last execution %OutIndex - output number. It relates to previous output or next depending on the `index_by_previous_line` option in the OutHist extension. """ _PROMPT_FORMAT = idleConf.GetOption("extensions", "CustomizePrompt", "prompt-format", type="str", default='>>> ', member_name="_PROMPT_FORMAT") last_prompt = None def __init__(self, editwin): def showprompt(self_): if CustomizePrompt.last_prompt: time_diff = time.time() - CustomizePrompt.last_prompt mins, secs = divmod(time_diff, 60) mins, wsecs = int(mins), int(secs) ms = int((secs - wsecs) * 100) else: mins, wsecs, ms = 0, 0, 0 s = CustomizePrompt._PROMPT_FORMAT.strip() s = s.replace('%dM', '%02d' % mins) s = s.replace('%dS', '%02d' % wsecs) s = s.replace('%df', '%02d' % ms) if '%OutIndex' in s and OutHist.enable: s = s.replace('%OutIndex', str(OutHist.cursor)) self_.resetoutput() s = time.strftime(s + ' ') sys.ps1 = s self_.console.write(s) self_.text.mark_set("insert", "end-1c") self_.set_line_and_column() self_.io.reset_undo() try: old_runcmd_from_source = editwin.interp.runcmd_from_source # In case it's a file editor window. except AttributeError: return def runcmd_from_source(self_, line): CustomizePrompt.last_prompt = time.time() return old_runcmd_from_source(line) editwin.showprompt = MethodType(showprompt, editwin) editwin.interp.runcmd_from_source = MethodType(runcmd_from_source, editwin.interp)
class ParenMatch: """Highlight matching parentheses There are three supported style of paren matching, based loosely on the Emacs options. The style is select based on the HILITE_STYLE attribute; it can be changed used the set_style method. The supported styles are: default -- When a right paren is typed, highlight the matching left paren for 1/2 sec. expression -- When a right paren is typed, highlight the entire expression from the left paren to the right paren. TODO: - extend IDLE with configuration dialog to change options - implement rest of Emacs highlight styles (see below) - print mismatch warning in IDLE status window Note: In Emacs, there are several styles of highlight where the matching paren is highlighted whenever the cursor is immediately to the right of a right paren. I don't know how to do that in Tk, so I haven't bothered. """ menudefs = [ ('edit', [ ("Show surrounding parens", "<<flash-paren>>"), ]) ] STYLE = idleConf.GetOption('extensions','ParenMatch','style', default='expression') FLASH_DELAY = idleConf.GetOption('extensions','ParenMatch','flash-delay', type='int',default=500) HILITE_CONFIG = idleConf.GetHighlight(idleConf.CurrentTheme(),'hilite') BELL = idleConf.GetOption('extensions','ParenMatch','bell', type='bool',default=1) RESTORE_VIRTUAL_EVENT_NAME = "<<parenmatch-check-restore>>" # We want the restore event be called before the usual return and # backspace events. RESTORE_SEQUENCES = ("<KeyPress>", "<ButtonPress>", "<Key-Return>", "<Key-BackSpace>") def __init__(self, editwin): self.editwin = editwin self.text = editwin.text # Bind the check-restore event to the function restore_event, # so that we can then use activate_restore (which calls event_add) # and deactivate_restore (which calls event_delete). editwin.text.bind(self.RESTORE_VIRTUAL_EVENT_NAME, self.restore_event) self.counter = 0 self.is_restore_active = 0 self.set_style(self.STYLE) def activate_restore(self): if not self.is_restore_active: for seq in self.RESTORE_SEQUENCES: self.text.event_add(self.RESTORE_VIRTUAL_EVENT_NAME, seq) self.is_restore_active = True def deactivate_restore(self): if self.is_restore_active: for seq in self.RESTORE_SEQUENCES: self.text.event_delete(self.RESTORE_VIRTUAL_EVENT_NAME, seq) self.is_restore_active = False def set_style(self, style): self.STYLE = style if style == "default": self.create_tag = self.create_tag_default self.set_timeout = self.set_timeout_last elif style == "expression": self.create_tag = self.create_tag_expression self.set_timeout = self.set_timeout_none def flash_paren_event(self, event): indices = (HyperParser(self.editwin, "insert") .get_surrounding_brackets()) if indices is None: self.warn_mismatched() return self.activate_restore() self.create_tag(indices) self.set_timeout_last() def paren_closed_event(self, event): # If it was a shortcut and not really a closing paren, quit. closer = self.text.get("insert-1c") if closer not in _openers: return hp = HyperParser(self.editwin, "insert-1c") if not hp.is_in_code(): return indices = hp.get_surrounding_brackets(_openers[closer], True) if indices is None: self.warn_mismatched() return self.activate_restore() self.create_tag(indices) self.set_timeout() def restore_event(self, event=None): self.text.tag_delete("paren") self.deactivate_restore() self.counter += 1 # disable the last timer, if there is one. def handle_restore_timer(self, timer_count): if timer_count == self.counter: self.restore_event() def warn_mismatched(self): if self.BELL: self.text.bell() # any one of the create_tag_XXX methods can be used depending on # the style def create_tag_default(self, indices): """Highlight the single paren that matches""" self.text.tag_add("paren", indices[0]) self.text.tag_config("paren", self.HILITE_CONFIG) def create_tag_expression(self, indices): """Highlight the entire expression""" if self.text.get(indices[1]) in (')', ']', '}'): rightindex = indices[1]+"+1c" else: rightindex = indices[1] self.text.tag_add("paren", indices[0], rightindex) self.text.tag_config("paren", self.HILITE_CONFIG) # any one of the set_timeout_XXX methods can be used depending on # the style def set_timeout_none(self): """Highlight will remain until user input turns it off or the insert has moved""" # After CHECK_DELAY, call a function which disables the "paren" tag # if the event is for the most recent timer and the insert has changed, # or schedules another call for itself. self.counter += 1 def callme(callme, self=self, c=self.counter, index=self.text.index("insert")): if index != self.text.index("insert"): self.handle_restore_timer(c) else: self.editwin.text_frame.after(CHECK_DELAY, callme, callme) self.editwin.text_frame.after(CHECK_DELAY, callme, callme) def set_timeout_last(self): """The last highlight created will be removed after .5 sec""" # associate a counter with an event; only disable the "paren" # tag if the event is for the most recent timer. self.counter += 1 self.editwin.text_frame.after( self.FLASH_DELAY, lambda self=self, c=self.counter: self.handle_restore_timer(c))
class CodeContext: menudefs = [('options', [('!Code Conte_xt', '<<toggle-code-context>>')])] context_depth = idleConf.GetOption("extensions", "CodeContext", "numlines", type="int", default=3) bgcolor = idleConf.GetOption("extensions", "CodeContext", "bgcolor", type="str", default="LightGray") fgcolor = idleConf.GetOption("extensions", "CodeContext", "fgcolor", type="str", default="Black") def __init__(self, editwin): self.editwin = editwin self.text = editwin.text self.textfont = self.text["font"] self.label = None # self.info is a list of (line number, indent level, line text, block # keyword) tuples providing the block structure associated with # self.topvisible (the linenumber of the line displayed at the top of # the edit window). self.info[0] is initialized as a 'dummy' line which # starts the toplevel 'block' of the module. self.info = [(0, -1, "", False)] self.topvisible = 1 visible = idleConf.GetOption("extensions", "CodeContext", "visible", type="bool", default=False) if visible: self.toggle_code_context_event() self.editwin.setvar('<<toggle-code-context>>', True) # Start two update cycles, one for context lines, one for font changes. self.text.after(UPDATEINTERVAL, self.timer_event) self.text.after(FONTUPDATEINTERVAL, self.font_timer_event) def toggle_code_context_event(self, event=None): if not self.label: # Calculate the border width and horizontal padding required to # align the context with the text in the main Text widget. # # All values are passed through int(str(<value>)), since some # values may be pixel objects, which can't simply be added to ints. widgets = self.editwin.text, self.editwin.text_frame # Calculate the required vertical padding padx = 0 for widget in widgets: padx += int(str(widget.pack_info()['padx'])) padx += int(str(widget.cget('padx'))) # Calculate the required border width border = 0 for widget in widgets: border += int(str(widget.cget('border'))) self.label = Tkinter.Label( self.editwin.top, text="\n" * (self.context_depth - 1), anchor=W, justify=LEFT, font=self.textfont, bg=self.bgcolor, fg=self.fgcolor, width=1, #don't request more than we get padx=padx, border=border, relief=SUNKEN) # Pack the label widget before and above the text_frame widget, # thus ensuring that it will appear directly above text_frame self.label.pack(side=TOP, fill=X, expand=False, before=self.editwin.text_frame) else: self.label.destroy() self.label = None idleConf.SetOption("extensions", "CodeContext", "visible", str(self.label is not None)) idleConf.SaveUserCfgFiles() def get_line_info(self, linenum): """Get the line indent value, text, and any block start keyword If the line does not start a block, the keyword value is False. The indentation of empty lines (or comment lines) is INFINITY. """ text = self.text.get("%d.0" % linenum, "%d.end" % linenum) spaces, firstword = getspacesfirstword(text) opener = firstword in BLOCKOPENERS and firstword if len(text) == len(spaces) or text[len(spaces)] == '#': indent = INFINITY else: indent = len(spaces) return indent, text, opener def get_context(self, new_topvisible, stopline=1, stopindent=0): """Get context lines, starting at new_topvisible and working backwards. Stop when stopline or stopindent is reached. Return a tuple of context data and the indent level at the top of the region inspected. """ assert stopline > 0 lines = [] # The indentation level we are currently in: lastindent = INFINITY # For a line to be interesting, it must begin with a block opening # keyword, and have less indentation than lastindent. for linenum in xrange(new_topvisible, stopline - 1, -1): indent, text, opener = self.get_line_info(linenum) if indent < lastindent: lastindent = indent if opener in ("else", "elif"): # We also show the if statement lastindent += 1 if opener and linenum < new_topvisible and indent >= stopindent: lines.append((linenum, indent, text, opener)) if lastindent <= stopindent: break lines.reverse() return lines, lastindent def update_code_context(self): """Update context information and lines visible in the context pane. """ new_topvisible = int(self.text.index("@0,0").split('.')[0]) if self.topvisible == new_topvisible: # haven't scrolled return if self.topvisible < new_topvisible: # scroll down lines, lastindent = self.get_context(new_topvisible, self.topvisible) # retain only context info applicable to the region # between topvisible and new_topvisible: while self.info[-1][1] >= lastindent: del self.info[-1] elif self.topvisible > new_topvisible: # scroll up stopindent = self.info[-1][1] + 1 # retain only context info associated # with lines above new_topvisible: while self.info[-1][0] >= new_topvisible: stopindent = self.info[-1][1] del self.info[-1] lines, lastindent = self.get_context(new_topvisible, self.info[-1][0] + 1, stopindent) self.info.extend(lines) self.topvisible = new_topvisible # empty lines in context pane: context_strings = [""] * max(0, self.context_depth - len(self.info)) # followed by the context hint lines: context_strings += [x[2] for x in self.info[-self.context_depth:]] self.label["text"] = '\n'.join(context_strings) def timer_event(self): if self.label: self.update_code_context() self.text.after(UPDATEINTERVAL, self.timer_event) def font_timer_event(self): newtextfont = self.text["font"] if self.label and newtextfont != self.textfont: self.textfont = newtextfont self.label["font"] = self.textfont self.text.after(FONTUPDATEINTERVAL, self.font_timer_event)
def encode(self, chars): if isinstance(chars, str): # This is either plain ASCII, or Tk was returning mixed-encoding # text to us. Don't try to guess further. return chars # See whether there is anything non-ASCII in it. # If not, no need to figure out the encoding. try: return chars.encode('ascii') except UnicodeError: pass # If there is an encoding declared, try this first. try: enc = coding_spec(chars) failed = None except LookupError as msg: failed = msg enc = None if enc: try: return chars.encode(enc) except UnicodeError: failed = "Invalid encoding '%s'" % enc if failed: tkMessageBox.showerror("I/O Error", "%s. Saving as UTF-8" % failed, parent=self.text) # If there was a UTF-8 signature, use that. This should not fail if self.fileencoding == BOM_UTF8 or failed: return BOM_UTF8 + chars.encode("utf-8") # Try the original file encoding next, if any if self.fileencoding: try: return chars.encode(self.fileencoding) except UnicodeError: tkMessageBox.showerror( "I/O Error", "Cannot save this as '%s' anymore. Saving as UTF-8" \ % self.fileencoding, parent = self.text) return BOM_UTF8 + chars.encode("utf-8") # Nothing was declared, and we had not determined an encoding # on loading. Recommend an encoding line. config_encoding = idleConf.GetOption("main", "EditorWindow", "encoding") if config_encoding == 'utf-8': # User has requested that we save files as UTF-8 return BOM_UTF8 + chars.encode("utf-8") ask_user = True try: chars = chars.encode(encoding) enc = encoding if config_encoding == 'locale': ask_user = False except UnicodeError: chars = BOM_UTF8 + chars.encode("utf-8") enc = "utf-8" if not ask_user: return chars dialog = EncodingMessage(self.editwin.top, enc) dialog.go() if dialog.num == 1: # User asked us to edit the file encline = "# -*- coding: %s -*-\n" % enc firstline = self.text.get("1.0", "2.0") if firstline.startswith("#!"): # Insert encoding after #! line self.text.insert("2.0", encline) else: self.text.insert("1.0", encline) return self.encode(self.text.get("1.0", "end-1c")) return chars