ฉันจะสร้าง Python Shell แบบ IDLE ขนาดเล็กใน Tkinter ได้อย่างไร


9

ฉันกำลังพยายามสร้างสิ่งที่ควบคุมโดย Python Shell GUI

สิ่งเดียวคือฉันไม่ทราบวิธีการทำสิ่งที่อินพุต / เอาต์พุตทั้งหมด ฉันแค่ต้องการพิมพ์อินพุตดำเนินการคำสั่ง python และให้เอาต์พุตของคำสั่ง python ฉันรู้ว่า IDLE ถูกสร้างขึ้นใน Tkinter ดังนั้นมันจึงใช้วิดเจ็ต

มันเป็นเพียงแค่ "อินพุตประเภทแสดงผลลัพธ์"

ฉันได้ลองค้นหา แต่ดูเหมือนว่าผลลัพธ์ส่วนใหญ่เกี่ยวข้องกับบรรทัดคำสั่งซึ่งไม่ใช่สิ่งที่ฉันกำลังมองหา อีกคำถามเดียวที่เหมือนกับของฉันไม่ใช่สิ่งที่ฉันมีอยู่ในใจเช่นกัน ฉันยังลอง loking รหัสแหล่งที่มาสำหรับ IDLE แต่ไม่พบสิ่งที่ฉันกำลังมองหา

ฉันพบคำตอบบางอย่างที่ใช้ได้กับ Linux แต่ฉันใช้งาน Windows 10 ...

ฉันต้องการ "shell" เพื่อให้อยู่ใน Tkinter เพราะด้านหนึ่งของหน้าจอจะเป็นอย่างอื่นที่เชื่อมต่อกับเอาต์พุตคำสั่ง

ไม่มีใครรู้ว่าเครื่องมือที่ใช้ในการทำเปลือกหลามง่ายมาก?


stackoverflow.com/questions/38977525/…อาจเป็นที่สนใจ
jasonharper

คำถามนั้นดูเหมือนว่าจะมีการติดตามของผู้อื่นซึ่งถูกลบไปแล้วมันก็ไม่สมเหตุสมผลกับฉันหากไม่มีบริบทจากคำถามที่ถูกลบ ... ดีอย่างน้อยฉันก็รู้ว่าตอนนี้เป็นไปได้ - และฉันไม่ใช่ มีเพียงคนเดียวที่พยายามทำบางสิ่งบางอย่าง ... อีกครั้ง
Eleeza the Character Wizard

1
ไม่ได้ใช้งานจะถูกเขียนในหลามใช้ tkinter ... อ่านรหัสที่มา
Reblochon Masque

คำตอบ:


13

Python Shell / Terminal / Command-Prompt แบบง่าย ๆ


  • ********************* มันอย่างแท้จริงเพียง " " สิ่งที่ ************************type inputshow output

import os
from tkinter import *
from subprocess import *


class PythonShell:

    def __init__(self):
        self.master = Tk()

        self.mem_cache = open("idle.txt", "w+")
        self.body = None
        self.entry = None
        self.button = None
        self.entry_content = None

    @staticmethod
    def welcome_note():
        """
        To show welcome note on tkinter window
        :return:
        """
        Label(text="Welcome To My Python Program [Version 1.0]", font='Arial 12', background="#272626",
              foreground="white").pack()

        Label(text=">> Insert Python Commands <<", font='Arial 12', background="#272626",
              foreground="white").pack()

    def get_text(self):
        """
        This method will perform following operations;
        1- Get text from body
        2- Implies python compilation (treat text as command)
        3- Set Output in Output-Entry

        :return: get and set text in body of text box
        """
        content = self.body.get(1.0, "end-1c")
        out_put = self.run_commands(content)
        self.entry_content.set(out_put)

    def store_commands(self, command=None):

        try:
            self.mem_cache.write(command + ';')
            self.mem_cache.close()

        except Exception as e:
            print(e)

    def get_stored_commands(self):
        try:
            with open("idle.txt", "r") as self.mem_cache:
                self.mem_cache.seek(0)
                val = self.mem_cache.read()
                self.mem_cache.close()
                return val

        except Exception as e:
            print(e)

    @staticmethod
    def check_if_file_empty():
        size = os.stat("idle.txt").st_size

        if size != 0:
            return True
        else:
            return False

    def run_commands(self, command):
        """

        This method would return output of every command place in text box
        :param command: python command from text box
        :return: output of command
        """

        print("Running command: {}".format(command))
        value = None
        new_line_char = command.find('\n')
        semi_colons_char = command.find(';')
        double_quote = command.find('"')

        try:
            if new_line_char != -1:

                if semi_colons_char != -1 & double_quote == -1:

                    new_cmd = command.replace("\n", "")
                    cmd_value = '"' + new_cmd + '"'
                    self.store_commands(command)

                    value = check_output("python -c " + cmd_value, shell=True).decode()
                elif semi_colons_char == -1 & double_quote == -1:

                    new_cmd = command.replace("\n", ";")
                    cmd_value = '"' + new_cmd + '"'
                    self.store_commands(command)
                    value = check_output("python -c " + cmd_value, shell=True).decode()

                elif double_quote != -1:

                    cmd_1 = command.replace('"', "'")
                    new_cmd = cmd_1.replace('\n', ';')

                    cmd_value = '"' + new_cmd + '"'
                    self.store_commands(command)

                    value = check_output("python -c " + cmd_value, shell=True).decode()

                elif self.body.compare("end-1c", "==", "1.0"):
                    self.entry_content.set("the widget is empty")

            elif self.body.compare("end-1c", "==", "1.0"):
                value = "The widget is empty. Please Enter Something."

            else:
                variable_analyzer = command.find('=')
                file_size = PythonShell.check_if_file_empty()

                if file_size:
                    new_cmd = command.replace('"', "'")
                    cmd_value = '"' + new_cmd + '"'
                    stored_value = self.get_stored_commands()
                    cmd = stored_value + cmd_value
                    cmd.replace('"', '')

                    value = check_output("python -c " + cmd, shell=True).decode()
                elif variable_analyzer != -1:
                    new_cmd = command.replace('"', "'")
                    cmd_value = '"' + new_cmd + '"'
                    self.store_commands(cmd_value)

                    value = 'Waiting for input...'
                    pass
                else:
                    new_cmd = command.replace('"', "'")
                    cmd_value = '"' + new_cmd + '"'
                    value = check_output("python -c " + cmd_value, shell=True).decode()

        except Exception as ex:
            print('>>>', ex)
            self.entry_content.set('Invalid Command. Try again!!!')

        print('>>', value)
        # To Clear Text body After Button Click
        # self.body.delete('1.0', END)

        return value

    def start_terminal(self):
        """
        Initiate tkinter session to place and run commands
        :return:
        """
        self.master.propagate(0)
        self.master.geometry('750x350')
        self.master.title('Python IDLE')
        self.master.configure(background='#272626')

        terminal.welcome_note()

        self.body = Text(self.master, height='10', width='75', font='Consolas 12', background="#272626",
                         foreground="white",
                         insertbackground='white')
        # self.body.propagate(0)
        self.body.pack(expand=True)

        Label(text=">> Command Output <<", font='Arial 12', background="#272626",
              foreground="white").pack()

        self.entry_content = StringVar()
        self.entry = Entry(self.master, textvariable=self.entry_content, width=50, font='Consolas 16',
                           background="white",
                           foreground="black")
        self.entry.pack()
        # self.entry.propagate(0)

        self.button = Button(self.master, text="Run Command", command=self.get_text, background="white",
                             foreground="black",
                             font='Helvetica 12').pack()

        self.master.mainloop()


if __name__ == '__main__':
    terminal = PythonShell()
    terminal.start_terminal()

สคริปต์ python ที่ระบุด้านบนมีลำดับชั้นตามที่กำหนด

    |import ...      
    |class PythonShell:
        |def __init__(self):...

        @staticmethod
        |def welcome_note():...
        |def get_text(self):...
        |def store_commands(self, commmand):...
        |def get_stored_commands(self):...

        @staticmethod
        |def check_if_file_empty():
        |def run_commands(self, command):...
        |def start_terminal(self):...

    |if __name__ == '__main__':...

ขั้นตอนการทำงาน:

เวิร์กโฟลว์พื้นฐานสำหรับโค้ดด้านบนได้รับดังต่อไปนี้

  • def welcome_note():... รวมถึงฉลากที่จะแสดงนอกเนื้อหาข้อความ

  • def get_text(self):...ดำเนินการสองอย่าง ** รับข้อความจากเนื้อความข้อความ ** & ** ตั้งค่าผลลัพธ์ในกล่องป้อนข้อมูล **

  • def store_commands(self, command):... ใช้เพื่อเก็บตัวแปรไว้ในไฟล์

  • def get_stored_commands(self):... รับตัวแปรที่เก็บไว้ในไฟล์

  • def check_if_file_empty():... ตรวจสอบขนาดของไฟล์

  • def run_commands(self, command):...วิธีการนี้ทำหน้าที่เป็นคอมไพเลอร์หลามที่รับคำสั่งทำการประมวลผลและให้ผลลัพธ์เอาต์พุตสำหรับคำสั่งที่กำหนด เพื่อรันคำสั่งฉันอยากจะแนะนำให้ใช้subprocess-moduleเพราะมันมีสิ่งอำนวยความสะดวกที่มีประสิทธิภาพมากขึ้นสำหรับการวางไข่กระบวนการใหม่และดึงผลลัพธ์ของพวกเขา; ในการรันคำสั่งหน้าต่างโดยใช้ python จะมีไลบรารี่ต่าง ๆ ในตัวเช่น;

    1. os ( โดยละเอียด ), 2. กระบวนการย่อย ( โดยละเอียด ) ฯลฯ

    การเช็คเอาต์ซึ่งดีกว่าที่จะใช้อ้างอิงการเยี่ยมชม: โมดูล subprocess- เป็นที่นิยมกว่า os-โมดูล

  • def start_terminal(self):...วิธีการนี้เกี่ยวข้องกับการทำงานเพื่อเริ่มต้นtkinterหน้าต่างเซสชั่นและแสดงเค้าโครงพื้นฐานสำหรับหน้าต่างอินพุตและเอาต์พุต

    คุณสามารถแก้ไขและปรับแต่งโค้ดนี้ได้ตามความต้องการของคุณ


Workaroud:

วินี้ใช้tkinter GUI based python shellงานฟังก์ชั่นการทำงานได้ง่ายเหมือน windows-command-prompt ในการรันคำสั่ง python directlyใน command-prompt โดยไม่ต้องย้ายไปยังเทอร์มินัล python เราทำได้ง่ายเช่น;

python -c "print('Hey Eleeza!!!')"

ผลลัพธ์จะง่ายเหมือน;

Hey Eleeza!!!

ในทำนองเดียวกันการรันมากกว่าหนึ่งบรรทัดโดยตรงในเวลาที่กำหนด;

python -c "import platform;sys_info=platform.uname();print(sys_info)"

มันจะเป็นเอาท์พุท;

My System Info: uname_result(system='Windows', node='DESKTOP-J75UTG5', release='10', version='10.0.18362', machine='AMD64', processor='Intel64 Family 6 Model 142 Stepping 10, GenuineIntel')

ดังนั้นในการใช้สิ่งนี้tkinter python shell;

  • คุณสามารถวางคำสั่งเป็น;

    import platform
    value=platform.uname()
    print('Value:', value)
  • หรือชอบวิธีนี้

    import platform;value=platform.uname();
    print('Value:', value)
  • หรือเพียงแค่คำสั่งอินไลน์เป็น

    import platform;value=platform.uname();print('Value:', value)

คุณจะได้รับผลลัพธ์เดียวกัน


1
อินพุต / เอาต์พุตนี้สมบูรณ์แบบขอบคุณมาก! มีเพียงสิ่งหนึ่งที่ถ้าฉันกำหนดตัวแปรให้รันคำสั่งแล้วล้างและลองพิมพ์ตัวแปรมันไม่ได้ทำอย่างนั้นฉันจะได้รับคำสั่งให้ทำงานในงูหลามจริง (ถ้าไม่ใช่ ทำอย่างนั้น)?
Eleeza the Character Wizard

2
@Eleeza คำถามแรกของคุณไม่มีส่วนเกี่ยวข้องกับข้อกำหนดประเภทนี้ มันเป็นพื้นฐานมากinput, output เปลือก python kernelมันทำหน้าที่เหมือนขั้นพื้นฐาน มันทำงานบนที่วางอยู่ในข้อความร่างกาย ฉันไม่ได้ตั้งค่าบัฟเฟอร์หรือหน่วยความจำแคชเพื่อเก็บประวัติตัวแปร ให้ฉันตรวจสอบข้อกำหนดนี้ก่อนใน IDLE นี้ก่อน!
มูฮัมหมัด Usman

1
สิ่งที่ฉันจะทำคือเกมที่ทั้งโลกตั้งโปรแกรมเป็นรหัสหลักและเครื่องจะใช้เพื่อสำรวจและโต้ตอบกับมัน ฉันคิดว่ามันจะเป็นกรณีของการรันโค้ดก่อนที่จะแสดงผลลัพธ์ แต่ฉันจะต้องตรวจสอบว่า
Eleeza the Character Wizard

2
อธิบายโดยละเอียด: ฉันกำลังสร้าง "โลก" ในโปรแกรมเกมหลักที่ซึ่งผู้คนสถานที่ ฯลฯ พวกเขาทั้งหมดเป็นวัตถุใน Python ผู้เล่นจะต้องสำรวจโลกผ่านเทอร์มินัล GUI โดยใช้คำสั่ง python มันเป็นเกมเกี่ยวกับการเรียนรู้ Python ผ่านการสำรวจ รหัสนี้ได้รับการแก้ไขในชีวิตจริง (แต่จะรีเซ็ตหลังจากนั้น)
Eleeza the Character Wizard

8
@Eleeza นี่คือที่เก็บ github ที่ดีที่สุดที่สามารถให้คุณติดตามอย่างที่คุณเป็น; เยี่ยมชมการอ้างอิง: github.com/codecombat/codecombat นี่คือบางส่วนอ้างอิงอื่น ๆ ที่คุณต้องดูเหล่านี้เช่นกัน github.com/replit/play , github.com/jatinmandav/Gaming-in-Python , github.com/PacktPublishing/-Learn-Python-Programming-with-Games , github.com/… , github.com/pyland/pylandนี่คืออีกหนึ่งการอ้างอิงเช่นกัน github.com/CharlesPikachu/Games
Muhammad Usman

4

นี่เป็นเชลล์แบบง่าย ๆ ส่วนใหญ่ที่ใช้exec()เพื่อประมวลผลคำสั่ง python และsubprocess.Popen()เพื่อรันคำสั่งภายนอก:

import tkinter as tk
import sys, io
import subprocess as subp
from contextlib import redirect_stdout

class Shell(tk.Text):
  def __init__(self, parent, **kwargs):
    tk.Text.__init__(self, parent, **kwargs)
    self.bind('<Key>', self.on_key) # setup handler to process pressed keys
    self.cmd = None        # hold the last command issued
    self.show_prompt()

  # to append given text at the end of Text box
  def insert_text(self, txt='', end='\n'):
    self.insert(tk.END, txt+end)
    self.see(tk.END) # make sure it is visible

  def show_prompt(self):
    self.insert_text('>> ', end='')
    self.mark_set(tk.INSERT, tk.END) # make sure the input cursor is at the end
    self.cursor = self.index(tk.INSERT) # save the input position

  # handler to process keyboard input
  def on_key(self, event):
    #print(event)
    if event.keysym == 'Up':
      if self.cmd:
        # show the last command
        self.delete(self.cursor, tk.END)
        self.insert(self.cursor, self.cmd)
      return "break" # disable the default handling of up key
    if event.keysym == 'Down':
      return "break" # disable the default handling of down key
    if event.keysym in ('Left', 'BackSpace'):
      current = self.index(tk.INSERT) # get the current position of the input cursor
      if self.compare(current, '==', self.cursor):
        # if input cursor is at the beginning of input (after the prompt), do nothing
        return "break"
    if event.keysym == 'Return':
      # extract the command input
      cmd = self.get(self.cursor, tk.END).strip()
      self.insert_text() # advance to next line
      if cmd.startswith('`'):
        # it is an external command
        self.system(cmd)
      else:
        # it is python statement
        self.execute(cmd)
      self.show_prompt()
      return "break" # disable the default handling of Enter key
    if event.keysym == 'Escape':
      self.master.destroy() # quit the shell

  # function to handle python statement input
  def execute(self, cmd):
    self.cmd = cmd  # save the command
    # use redirect_stdout() to capture the output of exec() to a string
    f = io.StringIO()
    with redirect_stdout(f):
      try:
        exec(self.cmd, globals())
      except Exception as e:
        print(e)
    # then append the output of exec() in the Text box
    self.insert_text(f.getvalue(), end='')

  # function to handle external command input
  def system(self, cmd):
    self.cmd = cmd  # save the command
    try:
      # extract the actual command
      cmd = cmd[cmd.index('`')+1:cmd.rindex('`')]
      proc = subp.Popen(cmd, stdout=subp.PIPE, stderr=subp.PIPE, text=True)
      stdout, stderr = proc.communicate(5) # get the command output
      # append the command output to Text box
      self.insert_text(stdout)
    except Exception as e:
      self.insert_text(str(e))

root = tk.Tk()
root.title('Simple Python Shell')

shell = Shell(root, width=100, height=50, font=('Consolas', 10))
shell.pack(fill=tk.BOTH, expand=1)
shell.focus_set()

root.mainloop()

เพียงป้อนคำสั่งหลามปกติ:

>> x = 1
>> print(x)
1

หรือป้อนคำสั่งเชลล์:

>> `cmd /c date /t`
2019-12-09

นอกจากนี้คุณยังสามารถใช้Upรหัสเพื่อเรียกคืนคำสั่งสุดท้าย

โปรดทราบว่าหากคุณดำเนินการคำสั่งระบบที่ต้องการให้ผู้ใช้ป้อนข้อมูลเชลล์จะหยุดการทำงานเป็นเวลา 5 วินาที (ระยะหมดเวลาที่ใช้communicate())

คุณสามารถปรับเปลี่ยนon_key()ฟังก์ชั่นให้เหมาะกับความต้องการของคุณ

โปรดรับการเตือนด้วยว่าการใช้งานexec()ไม่ใช่วิธีปฏิบัติที่ดี


คุณช่วยอธิบายรหัสผ่านได้มั้ย ฉันเป็นผู้เริ่มต้นเลย ... และฉันไม่รู้ว่าฉันเข้าใจถูกต้องหรือไม่
Eleeza the Character Wizard

1
ฉันได้เพิ่มความคิดเห็นในรหัสของฉันหวังว่าจะช่วย
acw1668

3

ฉันใช้ python shell โดยใช้code.InteractiveConsoleเพื่อรันคำสั่งสำหรับโครงการ ด้านล่างเป็นเวอร์ชั่นที่เรียบง่าย แต่ก็ยังค่อนข้างนานเพราะฉันได้เขียนการเชื่อมสำหรับคีย์พิเศษ (เช่น Return, Tab ... ) เพื่อให้ทำงานเหมือนในคอนโซลหลาม มันเป็นไปได้ที่จะเพิ่มคุณสมบัติเพิ่มเติมเช่นการเติมข้อความอัตโนมัติด้วยเจไดและไวยากรณ์ที่มีการเน้นด้วย pygments

แนวคิดหลักคือฉันใช้push()วิธีการcode.InteractiveConsoleเพื่อดำเนินการคำสั่ง เมธอดนี้ส่งคืนTrueหากเป็นคำสั่งบางส่วนเช่นdef test(x):และฉันใช้ความคิดเห็นนี้เพื่อแทรก...พรอมต์มิฉะนั้นเอาต์พุตจะปรากฏขึ้นและแสดง>>>พรอมต์ใหม่ contextlib.redirect_stdoutผมจับออกโดยใช้

นอกจากนี้ยังมีรหัสจำนวนมากที่เกี่ยวข้องกับเครื่องหมายและการเปรียบเทียบดัชนีเพราะฉันป้องกันผู้ใช้จากการแทรกข้อความภายในคำสั่งที่เรียกใช้ก่อนหน้านี้ แนวคิดคือฉันสร้างเครื่องหมาย 'อินพุต' ซึ่งบอกฉันว่าจุดเริ่มต้นของพรอมต์ที่ใช้งานอยู่และself.compare('insert', '<', 'input')ฉันรู้ได้เมื่อผู้ใช้พยายามแทรกข้อความด้านบนพร้อมท์ที่ใช้งานอยู่

import tkinter as tk
import sys
import re
from code import InteractiveConsole
from contextlib import redirect_stderr, redirect_stdout
from io import StringIO


class History(list):
    def __getitem__(self, index):
        try:
            return list.__getitem__(self, index)
        except IndexError:
            return


class TextConsole(tk.Text):
    def __init__(self, master, **kw):
        kw.setdefault('width', 50)
        kw.setdefault('wrap', 'word')
        kw.setdefault('prompt1', '>>> ')
        kw.setdefault('prompt2', '... ')
        banner = kw.pop('banner', 'Python %s\n' % sys.version)
        self._prompt1 = kw.pop('prompt1')
        self._prompt2 = kw.pop('prompt2')
        tk.Text.__init__(self, master, **kw)
        # --- history
        self.history = History()
        self._hist_item = 0
        self._hist_match = ''

        # --- initialization
        self._console = InteractiveConsole() # python console to execute commands
        self.insert('end', banner, 'banner')
        self.prompt()
        self.mark_set('input', 'insert')
        self.mark_gravity('input', 'left')

        # --- bindings
        self.bind('<Control-Return>', self.on_ctrl_return)
        self.bind('<Shift-Return>', self.on_shift_return)
        self.bind('<KeyPress>', self.on_key_press)
        self.bind('<KeyRelease>', self.on_key_release)
        self.bind('<Tab>', self.on_tab)
        self.bind('<Down>', self.on_down)
        self.bind('<Up>', self.on_up)
        self.bind('<Return>', self.on_return)
        self.bind('<BackSpace>', self.on_backspace)
        self.bind('<Control-c>', self.on_ctrl_c)
        self.bind('<<Paste>>', self.on_paste)

    def on_ctrl_c(self, event):
        """Copy selected code, removing prompts first"""
        sel = self.tag_ranges('sel')
        if sel:
            txt = self.get('sel.first', 'sel.last').splitlines()
            lines = []
            for i, line in enumerate(txt):
                if line.startswith(self._prompt1):
                    lines.append(line[len(self._prompt1):])
                elif line.startswith(self._prompt2):
                    lines.append(line[len(self._prompt2):])
                else:
                    lines.append(line)
            self.clipboard_clear()
            self.clipboard_append('\n'.join(lines))
        return 'break'

    def on_paste(self, event):
        """Paste commands"""
        if self.compare('insert', '<', 'input'):
            return "break"
        sel = self.tag_ranges('sel')
        if sel:
            self.delete('sel.first', 'sel.last')
        txt = self.clipboard_get()
        self.insert("insert", txt)
        self.insert_cmd(self.get("input", "end"))
        return 'break'

    def prompt(self, result=False):
        """Insert a prompt"""
        if result:
            self.insert('end', self._prompt2, 'prompt')
        else:
            self.insert('end', self._prompt1, 'prompt')
        self.mark_set('input', 'end-1c')

    def on_key_press(self, event):
        """Prevent text insertion in command history"""
        if self.compare('insert', '<', 'input') and event.keysym not in ['Left', 'Right']:
            self._hist_item = len(self.history)
            self.mark_set('insert', 'input lineend')
            if not event.char.isalnum():
                return 'break'

    def on_key_release(self, event):
        """Reset history scrolling"""
        if self.compare('insert', '<', 'input') and event.keysym not in ['Left', 'Right']:
            self._hist_item = len(self.history)
            return 'break'

    def on_up(self, event):
        """Handle up arrow key press"""
        if self.compare('insert', '<', 'input'):
            self.mark_set('insert', 'end')
            return 'break'
        elif self.index('input linestart') == self.index('insert linestart'):
            # navigate history
            line = self.get('input', 'insert')
            self._hist_match = line
            hist_item = self._hist_item
            self._hist_item -= 1
            item = self.history[self._hist_item]
            while self._hist_item >= 0 and not item.startswith(line):
                self._hist_item -= 1
                item = self.history[self._hist_item]
            if self._hist_item >= 0:
                index = self.index('insert')
                self.insert_cmd(item)
                self.mark_set('insert', index)
            else:
                self._hist_item = hist_item
            return 'break'

    def on_down(self, event):
        """Handle down arrow key press"""
        if self.compare('insert', '<', 'input'):
            self.mark_set('insert', 'end')
            return 'break'
        elif self.compare('insert lineend', '==', 'end-1c'):
            # navigate history
            line = self._hist_match
            self._hist_item += 1
            item = self.history[self._hist_item]
            while item is not None and not item.startswith(line):
                self._hist_item += 1
                item = self.history[self._hist_item]
            if item is not None:
                self.insert_cmd(item)
                self.mark_set('insert', 'input+%ic' % len(self._hist_match))
            else:
                self._hist_item = len(self.history)
                self.delete('input', 'end')
                self.insert('insert', line)
            return 'break'

    def on_tab(self, event):
        """Handle tab key press"""
        if self.compare('insert', '<', 'input'):
            self.mark_set('insert', 'input lineend')
            return "break"
        # indent code
        sel = self.tag_ranges('sel')
        if sel:
            start = str(self.index('sel.first'))
            end = str(self.index('sel.last'))
            start_line = int(start.split('.')[0])
            end_line = int(end.split('.')[0]) + 1
            for line in range(start_line, end_line):
                self.insert('%i.0' % line, '    ')
        else:
            txt = self.get('insert-1c')
            if not txt.isalnum() and txt != '.':
                self.insert('insert', '    ')
        return "break"

    def on_shift_return(self, event):
        """Handle Shift+Return key press"""
        if self.compare('insert', '<', 'input'):
            self.mark_set('insert', 'input lineend')
            return 'break'
        else: # execute commands
            self.mark_set('insert', 'end')
            self.insert('insert', '\n')
            self.insert('insert', self._prompt2, 'prompt')
            self.eval_current(True)

    def on_return(self, event=None):
        """Handle Return key press"""
        if self.compare('insert', '<', 'input'):
            self.mark_set('insert', 'input lineend')
            return 'break'
        else:
            self.eval_current(True)
            self.see('end')
        return 'break'

    def on_ctrl_return(self, event=None):
        """Handle Ctrl+Return key press"""
        self.insert('insert', '\n' + self._prompt2, 'prompt')
        return 'break'

    def on_backspace(self, event):
        """Handle delete key press"""
        if self.compare('insert', '<=', 'input'):
            self.mark_set('insert', 'input lineend')
            return 'break'
        sel = self.tag_ranges('sel')
        if sel:
            self.delete('sel.first', 'sel.last')
        else:
            linestart = self.get('insert linestart', 'insert')
            if re.search(r'    $', linestart):
                self.delete('insert-4c', 'insert')
            else:
                self.delete('insert-1c')
        return 'break'

    def insert_cmd(self, cmd):
        """Insert lines of code, adding prompts"""
        input_index = self.index('input')
        self.delete('input', 'end')
        lines = cmd.splitlines()
        if lines:
            indent = len(re.search(r'^( )*', lines[0]).group())
            self.insert('insert', lines[0][indent:])
            for line in lines[1:]:
                line = line[indent:]
                self.insert('insert', '\n')
                self.prompt(True)
                self.insert('insert', line)
                self.mark_set('input', input_index)
        self.see('end')

    def eval_current(self, auto_indent=False):
        """Evaluate code"""
        index = self.index('input')
        lines = self.get('input', 'insert lineend').splitlines() # commands to execute
        self.mark_set('insert', 'insert lineend')
        if lines:  # there is code to execute
            # remove prompts
            lines = [lines[0].rstrip()] + [line[len(self._prompt2):].rstrip() for line in lines[1:]]
            for i, l in enumerate(lines):
                if l.endswith('?'):
                    lines[i] = 'help(%s)' % l[:-1]
            cmds = '\n'.join(lines)
            self.insert('insert', '\n')
            out = StringIO()  # command output
            err = StringIO()  # command error traceback
            with redirect_stderr(err):     # redirect error traceback to err
                with redirect_stdout(out): # redirect command output
                    # execute commands in interactive console
                    res = self._console.push(cmds)
                    # if res is True, this is a partial command, e.g. 'def test():' and we need to wait for the rest of the code
            errors = err.getvalue()
            if errors:  # there were errors during the execution
                self.insert('end', errors)  # display the traceback
                self.mark_set('input', 'end')
                self.see('end')
                self.prompt() # insert new prompt
            else:
                output = out.getvalue()  # get output
                if output:
                    self.insert('end', output, 'output')
                self.mark_set('input', 'end')
                self.see('end')
                if not res and self.compare('insert linestart', '>', 'insert'):
                    self.insert('insert', '\n')
                self.prompt(res)
                if auto_indent and lines:
                    # insert indentation similar to previous lines
                    indent = re.search(r'^( )*', lines[-1]).group()
                    line = lines[-1].strip()
                    if line and line[-1] == ':':
                        indent = indent + '    '
                    self.insert('insert', indent)
                self.see('end')
                if res:
                    self.mark_set('input', index)
                    self._console.resetbuffer()  # clear buffer since the whole command will be retrieved from the text widget
                elif lines:
                    self.history.append(lines)  # add commands to history
                    self._hist_item = len(self.history)
            out.close()
            err.close()
        else:
            self.insert('insert', '\n')
            self.prompt()


if __name__ == '__main__':
    root = tk.Tk()
    console = TextConsole(root)
    console.pack(fill='both', expand=True)
    root.mainloop()
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.