From 5687dbe19512ac032ac1dddbe0616e3b24d4e089 Mon Sep 17 00:00:00 2001 From: XiaoBaiYun <71159641+littlewhitecloud@users.noreply.github.com> Date: Fri, 30 Jun 2023 15:32:36 +0800 Subject: [PATCH 01/34] I think it is useless, idk why I add it before --- .github/pull_request_template.md | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md deleted file mode 100644 index ada3dea..0000000 --- a/.github/pull_request_template.md +++ /dev/null @@ -1,14 +0,0 @@ -**Flags** - -**Tests** - - -**Note** - From 90744a5ef5cd5c031cb1b2dd6cf0f3f6fb7e4852 Mon Sep 17 00:00:00 2001 From: XiaoBaiYun <71159641+littlewhitecloud@users.noreply.github.com> Date: Fri, 30 Jun 2023 16:17:41 +0800 Subject: [PATCH 02/34] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2c5c3ea..52b4927 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@

TkTerminal

-[![PyPI](https://img.shields.io/pypi/v/tktermwidget)](https://pypi.org/project/tktermwidget) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) ![Platform](https://img.shields.io/powershellgallery/p/Pester?color=blue) +[![PyPI](https://img.shields.io/pypi/v/tktermwidget)](https://pypi.org/project/tktermwidget) +[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/python/black) ### 🌏 [简体中文](README_CH.md) From da01331d133a2f6e9d61ce44466cc8f10757d7d8 Mon Sep 17 00:00:00 2001 From: XiaoBaiYun <71159641+littlewhitecloud@users.noreply.github.com> Date: Fri, 30 Jun 2023 16:17:55 +0800 Subject: [PATCH 03/34] Update README_CH.md --- README_CH.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README_CH.md b/README_CH.md index 7b64c93..b6b638f 100644 --- a/README_CH.md +++ b/README_CH.md @@ -1,7 +1,9 @@

TkTerminal

-[![PyPI](https://img.shields.io/pypi/v/tktermwidget)](https://pypi.org/project/tktermwidget) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) ![Platform](https://img.shields.io/powershellgallery/p/Pester?color=blue) +[![PyPI](https://img.shields.io/pypi/v/tktermwidget)](https://pypi.org/project/tktermwidget) +[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/python/black) ```TkTermianl``` 是一个使用 tkinter 用 Python 编写的终端模拟器 From 4556674536cd09c549eb07467fcdc95ec7fb149f Mon Sep 17 00:00:00 2001 From: XiaoBaiYun <71159641+littlewhitecloud@users.noreply.github.com> Date: Sat, 8 Jul 2023 10:57:54 +0800 Subject: [PATCH 04/34] better logic than switch dev flag --- tktermwidget/tkterm.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tktermwidget/tkterm.py b/tktermwidget/tkterm.py index 95732ac..87e0b83 100644 --- a/tktermwidget/tkterm.py +++ b/tktermwidget/tkterm.py @@ -10,11 +10,10 @@ from platformdirs import user_cache_dir -dev: bool = False -if dev: - from style import DEFAULT -else: - from .style import DEFAULT # noqa: F401 +if __name__ == "__main__": + from style import DEFAULT # Beacuse it will fail when developing +else: # When call this file + from .style import DEFAULT # Set constants HISTORY_PATH = Path(user_cache_dir("tktermwidget")) From fd31b900d071e22e3f7c005b8bb8e4de3f81c275 Mon Sep 17 00:00:00 2001 From: XiaoBaiYun <71159641+littlewhitecloud@users.noreply.github.com> Date: Sat, 8 Jul 2023 10:58:33 +0800 Subject: [PATCH 05/34] of course --- tktermwidget/tkterm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tktermwidget/tkterm.py b/tktermwidget/tkterm.py index 87e0b83..e188ac8 100644 --- a/tktermwidget/tkterm.py +++ b/tktermwidget/tkterm.py @@ -13,7 +13,7 @@ if __name__ == "__main__": from style import DEFAULT # Beacuse it will fail when developing else: # When call this file - from .style import DEFAULT + from .style import DEFAULT # noqa: F401 # Set constants HISTORY_PATH = Path(user_cache_dir("tktermwidget")) From 8e61a6af96575f2f04058f6f7f0a24d605fc2ad5 Mon Sep 17 00:00:00 2001 From: XiaoBaiYun <71159641+littlewhitecloud@users.noreply.github.com> Date: Sat, 8 Jul 2023 10:59:24 +0800 Subject: [PATCH 06/34] black --- tktermwidget/tkterm.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tktermwidget/tkterm.py b/tktermwidget/tkterm.py index e188ac8..a88546f 100644 --- a/tktermwidget/tkterm.py +++ b/tktermwidget/tkterm.py @@ -10,10 +10,10 @@ from platformdirs import user_cache_dir -if __name__ == "__main__": - from style import DEFAULT # Beacuse it will fail when developing -else: # When call this file - from .style import DEFAULT # noqa: F401 +if __name__ == "__main__": + from style import DEFAULT +else: + from .style import DEFAULT # Set constants HISTORY_PATH = Path(user_cache_dir("tktermwidget")) From 267deb25bb6d37a5cf4087f3d42025ca50e0d97a Mon Sep 17 00:00:00 2001 From: XiaoBaiYun <71159641+littlewhitecloud@users.noreply.github.com> Date: Sat, 8 Jul 2023 11:00:22 +0800 Subject: [PATCH 07/34] Update tkterm.py --- tktermwidget/tkterm.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tktermwidget/tkterm.py b/tktermwidget/tkterm.py index a88546f..5f3a431 100644 --- a/tktermwidget/tkterm.py +++ b/tktermwidget/tkterm.py @@ -10,10 +10,10 @@ from platformdirs import user_cache_dir -if __name__ == "__main__": +if __name__ == "__main__": # Because it will fail when debugging on this file from style import DEFAULT -else: - from .style import DEFAULT +else: # Call from another file + from .style import DEFAULT # noqa: F401 # Set constants HISTORY_PATH = Path(user_cache_dir("tktermwidget")) From 61540a3c158ab2915ce53468d0d808e879e15b6c Mon Sep 17 00:00:00 2001 From: XiaoBaiYun <71159641+littlewhitecloud@users.noreply.github.com> Date: Sat, 8 Jul 2023 12:04:24 +0800 Subject: [PATCH 08/34] move check to __init__ --- tktermwidget/__init__.py | 35 ++++++++++++++++++++++++++++++++++- tktermwidget/style.py | 10 +--------- tktermwidget/tkterm.py | 26 ++++++++------------------ 3 files changed, 43 insertions(+), 28 deletions(-) diff --git a/tktermwidget/__init__.py b/tktermwidget/__init__.py index 2e3b19a..1453e17 100644 --- a/tktermwidget/__init__.py +++ b/tktermwidget/__init__.py @@ -1,3 +1,36 @@ -"""Tktermwidget package""" +"""__init__ of the tktermwidget package""" + +# Load the functions +from json import dump, load + +# Check files in need +from pathlib import Path + +from platformdirs import user_cache_dir + from .style import * # noqa: F401 from .tkterm import Terminal # noqa: F401 + +# Think we should put checks when load. +PACKAGE_PATH = Path(user_cache_dir("tktermwidget")) # Get the package path +HISTORY_FILE = PACKAGE_PATH / "history.txt" # Get the history file +JSON_FILE = PACKAGE_PATH / "styles.json" # Get the json file (style) + +if not PACKAGE_PATH.exists(): # Check the "tktermwidget" is exsit + PACKAGE_PATH.mkdir(parents=True) + # Also create the history file + with open(HISTORY_FILE, "w", encoding="utf-8") as f: + f.close() + # Also create the json file + with open(JSON_FILE, "w", encoding="utf-8") as f: + dump("{}", f) + +# Check that the history file exists +if not (HISTORY_FILE).exists(): + with open(HISTORY_FILE, "w", encoding="utf-8") as f: + f.close() + +# Check that the json file exists +if not (JSON_FILE).exists(): + with open(JSON_FILE, "w", encoding="utf-8") as f: + dump("{}", f) diff --git a/tktermwidget/style.py b/tktermwidget/style.py index ab36d49..d916678 100644 --- a/tktermwidget/style.py +++ b/tktermwidget/style.py @@ -56,15 +56,6 @@ "foreground": "#efefef", } -# Check the style file -if not STYLE_PATH.exists(): - STYLE_PATH.mkdir(parents=True) - with open(JSON_FILE, "w", encoding="utf-8") as f: - dump("{}", f) -if not (JSON_FILE).exists(): - with open(JSON_FILE, "w", encoding="utf-8") as f: - dump("{}", f) - # Functions def write_style(**styles) -> None: @@ -280,5 +271,6 @@ def checkhexcolor(self, event: Event, name: str) -> None: CUSTOM: dict[str] = load_style() if __name__ == "__main__": + # An example or a test configstyle = Config(True, basedon=POWERSHELL) configstyle.mainloop() diff --git a/tktermwidget/tkterm.py b/tktermwidget/tkterm.py index 5f3a431..84852d5 100644 --- a/tktermwidget/tkterm.py +++ b/tktermwidget/tkterm.py @@ -10,34 +10,21 @@ from platformdirs import user_cache_dir -if __name__ == "__main__": # Because it will fail when debugging on this file +if __name__ == "__main__": # Because it will fail when debugging on this file from style import DEFAULT -else: # Call from another file +else: # Call from another file from .style import DEFAULT # noqa: F401 # Set constants HISTORY_PATH = Path(user_cache_dir("tktermwidget")) HISTORY_FILE = HISTORY_PATH / "history.txt" SYSTEM = system() +CREATE_NEW_CONSOLE = 0 +SIGN = "$ " if SYSTEM == "Windows": from subprocess import CREATE_NEW_CONSOLE SIGN = ">" -else: - CREATE_NEW_CONSOLE = 0 - SIGN = "$ " - -# Check that the history directory exists -if not HISTORY_PATH.exists(): - HISTORY_PATH.mkdir(parents=True) - # Also create the history file - with open(HISTORY_FILE, "w", encoding="utf-8") as f: - f.close() - -# Check that the history file exists -if not (HISTORY_FILE).exists(): - with open(HISTORY_FILE, "w", encoding="utf-8") as f: - f.close() class AutoHideScrollbar(Scrollbar): @@ -125,7 +112,10 @@ def __init__( self.text.grid(row=0, column=0, sticky="nsew") self.yscroll.grid(row=0, column=1, sticky="ns") + # self.yscroll.grid(row=1 if horizontal else 0, column=0 if horizontal else 1, sticky="ns") + # ^ + # TODO: this line should check again | # Create command prompt self.directory() @@ -208,7 +198,7 @@ def down(self, _: Event) -> str: self.directory() return "break" - def left(self, _: Event) -> str: + def left(self, _: Event) -> str | None: """Go left in the command if the command is greater than the path""" insert_index = self.text.index("insert") dir_index = f"{insert_index.split('.')[0]}.{len(getcwd() + SIGN)}" From 05d750896b2cff48d0d12b18bd97ccfb0828500a Mon Sep 17 00:00:00 2001 From: XiaoBaiYun <71159641+littlewhitecloud@users.noreply.github.com> Date: Sat, 8 Jul 2023 12:06:01 +0800 Subject: [PATCH 09/34] Update __init__.py --- tktermwidget/__init__.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tktermwidget/__init__.py b/tktermwidget/__init__.py index 1453e17..967ce70 100644 --- a/tktermwidget/__init__.py +++ b/tktermwidget/__init__.py @@ -1,9 +1,5 @@ """__init__ of the tktermwidget package""" - -# Load the functions from json import dump, load - -# Check files in need from pathlib import Path from platformdirs import user_cache_dir From 429d8da0889c97ea90554b34c06ecb7b271231c7 Mon Sep 17 00:00:00 2001 From: XiaoBaiYun <71159641+littlewhitecloud@users.noreply.github.com> Date: Sat, 8 Jul 2023 12:12:55 +0800 Subject: [PATCH 10/34] nope --- .github/workflows/pages.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 9cd259c..aefe891 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -2,8 +2,8 @@ name: Deploy Jekyll with GitHub Pages dependencies preinstalled on: - # Runs on pushes targeting the default branch - push: + # Runs on pull_request targeting the default branch + pull_request: branches: ["main"] # Allows you to run this workflow manually from the Actions tab From da8a730047218250604949fcd8ebbe446ac28b2f Mon Sep 17 00:00:00 2001 From: XiaoBaiYun <71159641+littlewhitecloud@users.noreply.github.com> Date: Sat, 8 Jul 2023 12:17:12 +0800 Subject: [PATCH 11/34] Update pages.yml --- .github/workflows/pages.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index aefe891..465bdaf 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -2,9 +2,11 @@ name: Deploy Jekyll with GitHub Pages dependencies preinstalled on: - # Runs on pull_request targeting the default branch - pull_request: - branches: ["main"] + # Runs on pushes targeting the default branch + push: + branches: ["main"] # Only merge pull_request on main + # Don't create any single commit on main + # Get less github action use # Allows you to run this workflow manually from the Actions tab workflow_dispatch: From 8753a2b167b954bb563c5cbf42425ffe2333c3c2 Mon Sep 17 00:00:00 2001 From: XiaoBaiYun <71159641+littlewhitecloud@users.noreply.github.com> Date: Sat, 8 Jul 2023 17:02:43 +0800 Subject: [PATCH 12/34] Refact ```self.index``` (fix #49) and fix typo (fix #48) --- tktermwidget/tkterm.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tktermwidget/tkterm.py b/tktermwidget/tkterm.py index 84852d5..41ad767 100644 --- a/tktermwidget/tkterm.py +++ b/tktermwidget/tkterm.py @@ -154,12 +154,14 @@ def __init__( def check(self, _: Event) -> None: """Update cursor""" self.cursor = self.text.index("insert") + print("cur:", self.cursor) + print("late:", self.latest) if float(self.cursor) < float(self.latest): self.text.bind("", self.ignore, True) self.text.bind("", self.ignore, True) elif float(self.cursor) >= float(self.latest): - self.text.unbind("") - self.text.unbind("") + self.text.unbind("") + self.text.unbind("") def directory(self) -> None: """Insert the directory""" @@ -168,7 +170,6 @@ def directory(self) -> None: def newline(self) -> None: """Insert a newline""" self.text.insert("insert", "\n") - self.index += 1 def ignore(self, _: Event) -> str: """Ignore the event""" @@ -226,6 +227,7 @@ def loop(self, _: Event) -> str: cmd = self.text.get(f"{self.index}.0", "end-1c") cmd = cmd.split(SIGN)[-1].strip() + self.index += 1 if self.longflag: self.longcmd += cmd cmd = self.longcmd @@ -290,10 +292,9 @@ def loop(self, _: Event) -> str: self.newline() for line in returnlines: self.text.insert("insert", line) - if line == "\n": - self.index += 1 # Update the text and the index + self.index = int(self.text.index("insert").split('.')[0]) self.update() return "break" # Prevent the default newline character insertion From deb7529420bcd91556a8ba207d871bd1d7e39a85 Mon Sep 17 00:00:00 2001 From: XiaoBaiYun <71159641+littlewhitecloud@users.noreply.github.com> Date: Sat, 8 Jul 2023 17:35:50 +0800 Subject: [PATCH 13/34] black ruff --- tktermwidget/__init__.py | 6 +++++- tktermwidget/tkterm.py | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/tktermwidget/__init__.py b/tktermwidget/__init__.py index 967ce70..90b249f 100644 --- a/tktermwidget/__init__.py +++ b/tktermwidget/__init__.py @@ -1,5 +1,9 @@ """__init__ of the tktermwidget package""" -from json import dump, load + +# Load the functions +from json import dump + +# Check files in need from pathlib import Path from platformdirs import user_cache_dir diff --git a/tktermwidget/tkterm.py b/tktermwidget/tkterm.py index 41ad767..1be5d38 100644 --- a/tktermwidget/tkterm.py +++ b/tktermwidget/tkterm.py @@ -294,7 +294,7 @@ def loop(self, _: Event) -> str: self.text.insert("insert", line) # Update the text and the index - self.index = int(self.text.index("insert").split('.')[0]) + self.index = int(self.text.index("insert").split(".")[0]) self.update() return "break" # Prevent the default newline character insertion From 0a573f3fb7f1b268d25cd9af568c5e56d62d2239 Mon Sep 17 00:00:00 2001 From: XiaoBaiYun <71159641+littlewhitecloud@users.noreply.github.com> Date: Sat, 8 Jul 2023 17:40:08 +0800 Subject: [PATCH 14/34] Update __init__.py --- tktermwidget/__init__.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tktermwidget/__init__.py b/tktermwidget/__init__.py index 90b249f..b3d9331 100644 --- a/tktermwidget/__init__.py +++ b/tktermwidget/__init__.py @@ -1,9 +1,5 @@ """__init__ of the tktermwidget package""" - -# Load the functions from json import dump - -# Check files in need from pathlib import Path from platformdirs import user_cache_dir From 70c086713e39332770e5ae813ea22c478e06bc80 Mon Sep 17 00:00:00 2001 From: XiaoBaiYun <71159641+littlewhitecloud@users.noreply.github.com> Date: Sun, 9 Jul 2023 11:08:46 +0800 Subject: [PATCH 15/34] a bit improve... --- tktermwidget/__init__.py | 12 +++++----- tktermwidget/tkterm.py | 48 +++++++++++++++++----------------------- 2 files changed, 27 insertions(+), 33 deletions(-) diff --git a/tktermwidget/__init__.py b/tktermwidget/__init__.py index b3d9331..7fed88f 100644 --- a/tktermwidget/__init__.py +++ b/tktermwidget/__init__.py @@ -4,13 +4,15 @@ from platformdirs import user_cache_dir -from .style import * # noqa: F401 +from .style import * # noqa: F401, F403 from .tkterm import Terminal # noqa: F401 -# Think we should put checks when load. -PACKAGE_PATH = Path(user_cache_dir("tktermwidget")) # Get the package path -HISTORY_FILE = PACKAGE_PATH / "history.txt" # Get the history file -JSON_FILE = PACKAGE_PATH / "styles.json" # Get the json file (style) +# Get the package path +PACKAGE_PATH = Path(user_cache_dir("tktermwidget")) +# Get the history file +HISTORY_FILE = PACKAGE_PATH / "history.txt" +# Get the json file (style) +JSON_FILE = PACKAGE_PATH / "styles.json" if not PACKAGE_PATH.exists(): # Check the "tktermwidget" is exsit PACKAGE_PATH.mkdir(parents=True) diff --git a/tktermwidget/tkterm.py b/tktermwidget/tkterm.py index 1be5d38..044407d 100644 --- a/tktermwidget/tkterm.py +++ b/tktermwidget/tkterm.py @@ -47,8 +47,9 @@ class Terminal(Frame): Args: master (Misc): The parent widget - autohide (bool, optional): Whether to autohide the scrollbars. - (Set true to enable it.) + style (dict, optional): Set the style for the Terminal widget + filehistory (str, optional): Set your own file history instead of the normal + autohide (bool, optional): Whether to autohide the scrollbars. Set true to enable. *args: Arguments for the text widget **kwargs: Keyword arguments for the text widget @@ -106,6 +107,7 @@ def __init__( if horizontal: self.text.config(xscrollcommand=self.xscroll.set) self.xscroll.config(command=self.text.xview) + self.yscroll.config(command=self.text.yview) # Grid widgets @@ -115,21 +117,23 @@ def __init__( # self.yscroll.grid(row=1 if horizontal else 0, column=0 if horizontal else 1, sticky="ns") # ^ - # TODO: this line should check again | + # TODO: this line should check again | tag # Create command prompt self.directory() + # Set constants + self.longsymbol: str = "\\" if not SYSTEM == "Windows" else "&&" + self.filehistory: str = HISTORY_FILE if not filehistory else filehistory + # Set variables + self.index: int = 1 + self.longcmd: str = "" self.longflag: bool = False self.current_process: Popen | None = None - self.index: int = 1 self.cursor: int = self.text.index("insert") - self.longsymbol: str = "\\" if not SYSTEM == "Windows" else "&&" - self.longcmd: str = "" - self.filehistory: str = HISTORY_FILE if not filehistory else filehistory - self.latest: int = self.cursor + self.latest: int = self.cursor # Init as the insert index, lazy to write again # Bind events self.text.bind("", self.up, add=True) @@ -139,6 +143,7 @@ def __init__( self.text.bind(bind_str, self.left, add=True) for bind_str in ("", ""): self.text.bind(bind_str, self.check, add=True) + self.text.bind("", self.kill, add=True) # Isn't working # History recorder @@ -154,8 +159,6 @@ def __init__( def check(self, _: Event) -> None: """Update cursor""" self.cursor = self.text.index("insert") - print("cur:", self.cursor) - print("late:", self.latest) if float(self.cursor) < float(self.latest): self.text.bind("", self.ignore, True) self.text.bind("", self.ignore, True) @@ -214,7 +217,7 @@ def kill(self, _: Event) -> str: return "break" def update(self) -> str: - """Update or the command has no output""" + """Update the text widget or the command has no output""" self.directory() self.check(None) self.latest = self.text.index("insert") @@ -223,11 +226,12 @@ def update(self) -> str: def loop(self, _: Event) -> str: """Create an input loop""" - # Get the command from the text + # Get the line from the text cmd = self.text.get(f"{self.index}.0", "end-1c") + # Split the command from the line cmd = cmd.split(SIGN)[-1].strip() + self.index += 1 # If return "break" - self.index += 1 if self.longflag: self.longcmd += cmd cmd = self.longcmd @@ -285,9 +289,11 @@ def loop(self, _: Event) -> str: errors: str = output[1] returncode = self.current_process.returncode self.current_process = None + if returncode != 0: returnlines += errors # If the command was unsuccessful, it doesn't give stdout # TODO: Get the success message from the command (see #16) + # Output to the text self.newline() for line in returnlines: @@ -302,37 +308,23 @@ def loop(self, _: Event) -> str: if __name__ == "__main__": from tkinter import Tk - # Create root window root = Tk() - - # Hide root window during initialization root.withdraw() - - # Set title root.title("Terminal") - # Create terminal term = Terminal(root) term.pack(expand=True, fill="both") - # Set minimum size and center app - - # Update widgets so minimum size is accurate root.update_idletasks() - # Set the minimum size minimum_width: int = root.winfo_reqwidth() minimum_height: int = root.winfo_reqheight() - # Get center of screen based on minimum size x_coords = int(root.winfo_screenwidth() / 2 - minimum_width / 2) y_coords = int(root.wm_maxsize()[1] / 2 - minimum_height / 2) - # Place app and make the minimum size the actual minimum size (non-infringable) + root.geometry(f"{minimum_width}x{minimum_height}+{x_coords}+{y_coords}") root.wm_minsize(minimum_width, minimum_height) - # Show root window root.deiconify() - - # Start mainloop root.mainloop() From 5227691b4fdddd16397f4e7a9e03fc8c30f2f7c1 Mon Sep 17 00:00:00 2001 From: XiaoBaiYun <71159641+littlewhitecloud@users.noreply.github.com> Date: Wed, 12 Jul 2023 09:28:24 +0800 Subject: [PATCH 16/34] LOL rebuild ! --- __init__.py | 34 ++++++ widgets.py | 316 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 350 insertions(+) create mode 100644 __init__.py create mode 100644 widgets.py diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..f00a52c --- /dev/null +++ b/__init__.py @@ -0,0 +1,34 @@ +"""__init__ of the tktermwidget package""" +from json import dump +from pathlib import Path + +from platformdirs import user_cache_dir + +from .style import * # noqa: F401, F403 +from .widgets import Terminal # noqa: F401 + +# Get the package path +PACKAGE_PATH = Path(user_cache_dir("tktermwidget")) +# Get the history file +HISTORY_FILE = PACKAGE_PATH / "history.txt" +# Get the json file (style) +JSON_FILE = PACKAGE_PATH / "styles.json" + +if not PACKAGE_PATH.exists(): # Check the "tktermwidget" is exsit + PACKAGE_PATH.mkdir(parents=True) + # Also create the history file + with open(HISTORY_FILE, "w", encoding="utf-8") as f: + f.close() + # Also create the json file + with open(JSON_FILE, "w", encoding="utf-8") as f: + dump("{}", f) + +# Check that the history file exists +if not (HISTORY_FILE).exists(): + with open(HISTORY_FILE, "w", encoding="utf-8") as f: + f.close() + +# Check that the json file exists +if not (JSON_FILE).exists(): + with open(JSON_FILE, "w", encoding="utf-8") as f: + dump("{}", f) diff --git a/widgets.py b/widgets.py new file mode 100644 index 0000000..099308a --- /dev/null +++ b/widgets.py @@ -0,0 +1,316 @@ +"""Tkinter Terminal widget""" +from __future__ import annotations + +from os import chdir, getcwd +from pathlib import Path +from platform import system +from subprocess import PIPE, Popen +from tkinter import Event, Misc, Text +from tkinter.ttk import Frame, Scrollbar + +from platformdirs import user_cache_dir + +if __name__ == "__main__": from style import DEFAULT +else: from .style import DEFAULT + +HISTORY_PATH = Path(user_cache_dir("tktermwidget")) +HISTORY_FILE = HISTORY_PATH / "history.txt" +SYSTEM = system() +CREATE_NEWCONSOLE = 0 +SIGN = "$ " + +if SYSTEM == "Windows": # Check if platform is windows + from subprocess import CREATE_NEW_CONSOLE + + SIGN = ">" + +class AutoHideScrollbar(Scrollbar): + """Scrollbar that automatically hides when not needed""" + + def __init__(self, master=None, **kwargs): + Scrollbar.__init__(self, master=master, **kwargs) + + def set(self, first: int, last: int): + """Set the Scrollbar""" + if float(first) <= 0.0 and float(last) >= 1.0: + self.grid_remove() + else: + self.grid() + Scrollbar.set(self, first, last) + + +class Terminal(Frame): + """A terminal widget for tkinter applications + + Args: + master (Misc): The parent widget + style (dict, optional): Set the style for the Terminal widget + filehistory (str, optional): Set your own file history instead of the normal + autohide (bool, optional): Whether to autohide the scrollbars. Set true to enable. + *args: Arguments for the text widget + **kwargs: Keyword argu ments for the text widget + + Methods for outside use: + None + + Methods for internal use: + up (Event) -> str: Goes up in the history + down (Event) -> str: Goes down in the history + (If the user is at the bottom of the history, it clears the command) + left (Event) -> str: Goes left in the command if the index is greater than the directory + (So the user can't delete the directory or go left of it) + kill (Event) -> str: Kills the current command + loop (Event) -> str: Runs the command typed""" + + def __init__(self, master: Misc, style: dict = DEFAULT, filehistory: str = None, autohide: bool = False, *args, **kwargs): + Frame.__init__(self, master) + + # Set row and column weights + self.rowconfigure(0, weight=1) + self.columnconfigure(0, weight=1) + + # Create text widget and x, y scrollbar + self.style: dict = style + horizontal: bool = False + scrollbar = Scrollbar if not autohide else AutoHideScrollbar + + if kwargs.get("wrap", "char") == "none": + self.xscroll = scrollbar(self, orient="horizontal") + horizontal = True + + self.yscroll = scrollbar(self) + self.text = Text( + self, + *args, + wrap=kwargs.get("wrap", "char"), + yscrollcommand=self.yscroll.set, + relief=kwargs.get("relief", "flat"), + font=kwargs.get("font", ("Cascadia Code", 9, "normal")), + foreground=kwargs.get("foreground", self.style["foreground"]), + background=kwargs.get("background", self.style["background"]), + insertbackground=kwargs.get("insertbackground", self.style["insertbackground"]), + selectbackground=kwargs.get("selectbackground", self.style["selectbackground"]), + selectforeground=kwargs.get("selectforeground", self.style["selectforeground"]), + ) + + if horizontal: + self.text.config(xscrollcommand=self.xscroll.set) + self.xscroll.config(command=self.text.xview) + self.yscroll.config(command=self.text.yview) + + # Grid widgets + self.text.grid(row=0, column=0, sticky="nsew") + if horizontal: + self.xscroll.grid(row=1, column=0, sticky="ew") + self.yscroll.grid(row=0, column=1, sticky="ns") + + # Init command prompt + self.directory() + + # Set constants + self.longsymbol: str = "\\" if not SYSTEM == "Windows" else "&&" + self.filehistory: str = HISTORY_FILE if not filehistory else filehistory + + # Set variables + self.index: int = 1 + self.longcmd: str = "" + self.longflag: bool = False + self.current_process: Popen | None = None + self.cursor: int = self.text.index("insert") + + self.latest: int = self.cursor + + # Bind events + self.text.bind("", self.up, add=True) + self.text.bind("", self.down, add=True) + self.text.bind("", self.execute, add=True) + for bind_str in ("", ""): + self.text.bind(bind_str, self.left, add=True) + for bind_str in ("", "", "", ""): + self.text.bind(bind_str, self.check, add=True) + + self.text.bind("", self.kill, add=True) # Isn't working + + # History recorder + self.history = open( + self.filehistory, + "r+", # Both read and write + encoding="utf-8", + ) + + self.historys = [i.strip() for i in self.history.readlines()] + self.historyindex = len(self.historys) - 1 + + def check(self, _: Event) -> None: + """Update cursor and check if it is out of the edit range""" + self.cursor = self.text.index("insert") # Update cursor + if float(self.cursor) < float(self.latest): + for bind_str in ("", ""): + self.text.bind(bind_str, lambda _: "break", add=True) + else: + for unbind_str in ("", ""): + self.text.unbind(unbind_str) + + def directory(self) -> None: + """Insert the directory""" + self.text.insert( + "insert", + getcwd() + SIGN + ) + + def kill(self, _: Evnet) -> str: + """Kill the current process""" + if self.current_process: + self.current_process.terminate() + self.current_process = None + return "break" + + def execute(self, _: Event) -> str: + """Execute the command""" + # Get the line from the text + cmd = self.text.get(f"{self.index}.0", "end-1c") + # Split the command from the line also strip + cmd = cmd.split(SIGN)[-1].strip() + self.index += 1 # if some "if" statement return "break" + + if cmd.endswith(self.longsymbol): + self.longcmd += cmd.split(self.longsymbol)[0] + self.longflag = True + self.newline() + return "break" + + if self.longflag: + cmd = self.longcmd + cmd + self.longcmd = "" + self.longflag = False + + if cmd: # Record the command if it isn't empty + self.history.write(cmd + "\n") + self.historys.append(cmd) + self.historyindex = len(self.historys) - 1 + else: # Leave the loop + self.newline() + self.directory() + self.text.see("end") + return "break" + + # Check the command if it is a special command + if cmd in ["clear", "cls"]: + self.text.delete("1.0", "end") + self.directory() + return "break" + elif cmd == "exit": + self.master.quit() + + # Set the insert position is at the end + self.text.mark_set("insert", f"{self.index}.end") + self.text.see("insert") + + # TODO: Refactor the way we get output from subprocess + # Run the command + self.current_process = Popen( + cmd, + shell=True, + stdout=PIPE, + stderr=PIPE, + stdin=PIPE, + text=True, + cwd=getcwd(), # TODO: use dynamtic path instead (see #35) + creationflags=CREATE_NEW_CONSOLE, + ) + # The following needs to be put in an after so the kill command works + + # Check if the command was successful + output: tuple = self.current_process.communicate() + returnlines: str = output[0] + errors: str = output[1] + returncode = self.current_process.returncode + self.current_process = None + + if returncode != 0: + returnlines += errors # If the command was unsuccessful, it doesn't give stdout + # TODO: Get the success message from the command (see #16) + + # Output to the text + self.newline() + for line in returnlines: + self.text.insert("insert", line) + + # Update the text and the index + self.index = int(self.text.index("insert").split(".")[0]) + self.update() + return "break" # Prevent the default newline character insertion + + + def newline(self) -> None: + """Insert a newline""" + self.text.insert("insert", "\n") + + def update(self) -> str: + """Update the text widget or the command has no output""" + # Insert the directory + self.directory() + # Update cursor and check if it is out of the edit range + self.check(None) + # Update latest index + self.latest = self.text.index("insert") + # Warp to the end + self.text.see("end") + return "break" + + # Keypress + def down(self, _: Event) -> str: + """Go down in the history""" + if self.historyindex < len(self.historys) - 1: + self.text.delete(f"{self.index}.0", "end-1c") + self.directory() + # Insert the command + self.text.insert("insert", self.historys[self.historyindex]) + self.historyindex += 1 + else: + # Clear the command + self.text.delete(f"{self.index}.0", "end-1c") + self.directory() + return "break" + + def left(self, _: Event) -> str | None: + """Go left in the command if the command is greater than the path""" + insert_index = self.text.index("insert") + dir_index = f"{insert_index.split('.')[0]}.{len(getcwd() + SIGN)}" + if insert_index == dir_index: + return "break" + + def up(self, _: Event) -> str: + """Go up in the history""" + if self.historyindex >= 0: + self.text.delete(f"{self.index}.0", "end-1c") + self.directory() + # Insert the command + self.text.insert("insert", self.historys[self.historyindex]) + self.historyindex -= 1 + return "break" + + +if __name__ == "__main__": + from tkinter import Tk + + root = Tk() + root.withdraw() + root.title("Terminal") + + term = Terminal(root) + term.pack(expand=True, fill="both") + + root.update_idletasks() + + minimum_width: int = root.winfo_reqwidth() + minimum_height: int = root.winfo_reqheight() + + x_coords = int(root.winfo_screenwidth() / 2 - minimum_width / 2) + y_coords = int(root.wm_maxsize()[1] / 2 - minimum_height / 2) + + root.geometry(f"{minimum_width}x{minimum_height}+{x_coords}+{y_coords}") + root.wm_minsize(minimum_width, minimum_height) + + root.deiconify() + root.mainloop() From 2127bf79b9a05aee2eed7fa2bfbb85ca57a955d3 Mon Sep 17 00:00:00 2001 From: XiaoBaiYun <71159641+littlewhitecloud@users.noreply.github.com> Date: Wed, 12 Jul 2023 09:28:58 +0800 Subject: [PATCH 17/34] s, w p --- __init__.py | 34 ---------------------------------- 1 file changed, 34 deletions(-) delete mode 100644 __init__.py diff --git a/__init__.py b/__init__.py deleted file mode 100644 index f00a52c..0000000 --- a/__init__.py +++ /dev/null @@ -1,34 +0,0 @@ -"""__init__ of the tktermwidget package""" -from json import dump -from pathlib import Path - -from platformdirs import user_cache_dir - -from .style import * # noqa: F401, F403 -from .widgets import Terminal # noqa: F401 - -# Get the package path -PACKAGE_PATH = Path(user_cache_dir("tktermwidget")) -# Get the history file -HISTORY_FILE = PACKAGE_PATH / "history.txt" -# Get the json file (style) -JSON_FILE = PACKAGE_PATH / "styles.json" - -if not PACKAGE_PATH.exists(): # Check the "tktermwidget" is exsit - PACKAGE_PATH.mkdir(parents=True) - # Also create the history file - with open(HISTORY_FILE, "w", encoding="utf-8") as f: - f.close() - # Also create the json file - with open(JSON_FILE, "w", encoding="utf-8") as f: - dump("{}", f) - -# Check that the history file exists -if not (HISTORY_FILE).exists(): - with open(HISTORY_FILE, "w", encoding="utf-8") as f: - f.close() - -# Check that the json file exists -if not (JSON_FILE).exists(): - with open(JSON_FILE, "w", encoding="utf-8") as f: - dump("{}", f) From 2d12f27b6f08a2aaa4760e63c71fd487e8cd880d Mon Sep 17 00:00:00 2001 From: XiaoBaiYun <71159641+littlewhitecloud@users.noreply.github.com> Date: Wed, 12 Jul 2023 09:29:11 +0800 Subject: [PATCH 18/34] s, w p --- widgets.py | 316 ----------------------------------------------------- 1 file changed, 316 deletions(-) delete mode 100644 widgets.py diff --git a/widgets.py b/widgets.py deleted file mode 100644 index 099308a..0000000 --- a/widgets.py +++ /dev/null @@ -1,316 +0,0 @@ -"""Tkinter Terminal widget""" -from __future__ import annotations - -from os import chdir, getcwd -from pathlib import Path -from platform import system -from subprocess import PIPE, Popen -from tkinter import Event, Misc, Text -from tkinter.ttk import Frame, Scrollbar - -from platformdirs import user_cache_dir - -if __name__ == "__main__": from style import DEFAULT -else: from .style import DEFAULT - -HISTORY_PATH = Path(user_cache_dir("tktermwidget")) -HISTORY_FILE = HISTORY_PATH / "history.txt" -SYSTEM = system() -CREATE_NEWCONSOLE = 0 -SIGN = "$ " - -if SYSTEM == "Windows": # Check if platform is windows - from subprocess import CREATE_NEW_CONSOLE - - SIGN = ">" - -class AutoHideScrollbar(Scrollbar): - """Scrollbar that automatically hides when not needed""" - - def __init__(self, master=None, **kwargs): - Scrollbar.__init__(self, master=master, **kwargs) - - def set(self, first: int, last: int): - """Set the Scrollbar""" - if float(first) <= 0.0 and float(last) >= 1.0: - self.grid_remove() - else: - self.grid() - Scrollbar.set(self, first, last) - - -class Terminal(Frame): - """A terminal widget for tkinter applications - - Args: - master (Misc): The parent widget - style (dict, optional): Set the style for the Terminal widget - filehistory (str, optional): Set your own file history instead of the normal - autohide (bool, optional): Whether to autohide the scrollbars. Set true to enable. - *args: Arguments for the text widget - **kwargs: Keyword argu ments for the text widget - - Methods for outside use: - None - - Methods for internal use: - up (Event) -> str: Goes up in the history - down (Event) -> str: Goes down in the history - (If the user is at the bottom of the history, it clears the command) - left (Event) -> str: Goes left in the command if the index is greater than the directory - (So the user can't delete the directory or go left of it) - kill (Event) -> str: Kills the current command - loop (Event) -> str: Runs the command typed""" - - def __init__(self, master: Misc, style: dict = DEFAULT, filehistory: str = None, autohide: bool = False, *args, **kwargs): - Frame.__init__(self, master) - - # Set row and column weights - self.rowconfigure(0, weight=1) - self.columnconfigure(0, weight=1) - - # Create text widget and x, y scrollbar - self.style: dict = style - horizontal: bool = False - scrollbar = Scrollbar if not autohide else AutoHideScrollbar - - if kwargs.get("wrap", "char") == "none": - self.xscroll = scrollbar(self, orient="horizontal") - horizontal = True - - self.yscroll = scrollbar(self) - self.text = Text( - self, - *args, - wrap=kwargs.get("wrap", "char"), - yscrollcommand=self.yscroll.set, - relief=kwargs.get("relief", "flat"), - font=kwargs.get("font", ("Cascadia Code", 9, "normal")), - foreground=kwargs.get("foreground", self.style["foreground"]), - background=kwargs.get("background", self.style["background"]), - insertbackground=kwargs.get("insertbackground", self.style["insertbackground"]), - selectbackground=kwargs.get("selectbackground", self.style["selectbackground"]), - selectforeground=kwargs.get("selectforeground", self.style["selectforeground"]), - ) - - if horizontal: - self.text.config(xscrollcommand=self.xscroll.set) - self.xscroll.config(command=self.text.xview) - self.yscroll.config(command=self.text.yview) - - # Grid widgets - self.text.grid(row=0, column=0, sticky="nsew") - if horizontal: - self.xscroll.grid(row=1, column=0, sticky="ew") - self.yscroll.grid(row=0, column=1, sticky="ns") - - # Init command prompt - self.directory() - - # Set constants - self.longsymbol: str = "\\" if not SYSTEM == "Windows" else "&&" - self.filehistory: str = HISTORY_FILE if not filehistory else filehistory - - # Set variables - self.index: int = 1 - self.longcmd: str = "" - self.longflag: bool = False - self.current_process: Popen | None = None - self.cursor: int = self.text.index("insert") - - self.latest: int = self.cursor - - # Bind events - self.text.bind("", self.up, add=True) - self.text.bind("", self.down, add=True) - self.text.bind("", self.execute, add=True) - for bind_str in ("", ""): - self.text.bind(bind_str, self.left, add=True) - for bind_str in ("", "", "", ""): - self.text.bind(bind_str, self.check, add=True) - - self.text.bind("", self.kill, add=True) # Isn't working - - # History recorder - self.history = open( - self.filehistory, - "r+", # Both read and write - encoding="utf-8", - ) - - self.historys = [i.strip() for i in self.history.readlines()] - self.historyindex = len(self.historys) - 1 - - def check(self, _: Event) -> None: - """Update cursor and check if it is out of the edit range""" - self.cursor = self.text.index("insert") # Update cursor - if float(self.cursor) < float(self.latest): - for bind_str in ("", ""): - self.text.bind(bind_str, lambda _: "break", add=True) - else: - for unbind_str in ("", ""): - self.text.unbind(unbind_str) - - def directory(self) -> None: - """Insert the directory""" - self.text.insert( - "insert", - getcwd() + SIGN - ) - - def kill(self, _: Evnet) -> str: - """Kill the current process""" - if self.current_process: - self.current_process.terminate() - self.current_process = None - return "break" - - def execute(self, _: Event) -> str: - """Execute the command""" - # Get the line from the text - cmd = self.text.get(f"{self.index}.0", "end-1c") - # Split the command from the line also strip - cmd = cmd.split(SIGN)[-1].strip() - self.index += 1 # if some "if" statement return "break" - - if cmd.endswith(self.longsymbol): - self.longcmd += cmd.split(self.longsymbol)[0] - self.longflag = True - self.newline() - return "break" - - if self.longflag: - cmd = self.longcmd + cmd - self.longcmd = "" - self.longflag = False - - if cmd: # Record the command if it isn't empty - self.history.write(cmd + "\n") - self.historys.append(cmd) - self.historyindex = len(self.historys) - 1 - else: # Leave the loop - self.newline() - self.directory() - self.text.see("end") - return "break" - - # Check the command if it is a special command - if cmd in ["clear", "cls"]: - self.text.delete("1.0", "end") - self.directory() - return "break" - elif cmd == "exit": - self.master.quit() - - # Set the insert position is at the end - self.text.mark_set("insert", f"{self.index}.end") - self.text.see("insert") - - # TODO: Refactor the way we get output from subprocess - # Run the command - self.current_process = Popen( - cmd, - shell=True, - stdout=PIPE, - stderr=PIPE, - stdin=PIPE, - text=True, - cwd=getcwd(), # TODO: use dynamtic path instead (see #35) - creationflags=CREATE_NEW_CONSOLE, - ) - # The following needs to be put in an after so the kill command works - - # Check if the command was successful - output: tuple = self.current_process.communicate() - returnlines: str = output[0] - errors: str = output[1] - returncode = self.current_process.returncode - self.current_process = None - - if returncode != 0: - returnlines += errors # If the command was unsuccessful, it doesn't give stdout - # TODO: Get the success message from the command (see #16) - - # Output to the text - self.newline() - for line in returnlines: - self.text.insert("insert", line) - - # Update the text and the index - self.index = int(self.text.index("insert").split(".")[0]) - self.update() - return "break" # Prevent the default newline character insertion - - - def newline(self) -> None: - """Insert a newline""" - self.text.insert("insert", "\n") - - def update(self) -> str: - """Update the text widget or the command has no output""" - # Insert the directory - self.directory() - # Update cursor and check if it is out of the edit range - self.check(None) - # Update latest index - self.latest = self.text.index("insert") - # Warp to the end - self.text.see("end") - return "break" - - # Keypress - def down(self, _: Event) -> str: - """Go down in the history""" - if self.historyindex < len(self.historys) - 1: - self.text.delete(f"{self.index}.0", "end-1c") - self.directory() - # Insert the command - self.text.insert("insert", self.historys[self.historyindex]) - self.historyindex += 1 - else: - # Clear the command - self.text.delete(f"{self.index}.0", "end-1c") - self.directory() - return "break" - - def left(self, _: Event) -> str | None: - """Go left in the command if the command is greater than the path""" - insert_index = self.text.index("insert") - dir_index = f"{insert_index.split('.')[0]}.{len(getcwd() + SIGN)}" - if insert_index == dir_index: - return "break" - - def up(self, _: Event) -> str: - """Go up in the history""" - if self.historyindex >= 0: - self.text.delete(f"{self.index}.0", "end-1c") - self.directory() - # Insert the command - self.text.insert("insert", self.historys[self.historyindex]) - self.historyindex -= 1 - return "break" - - -if __name__ == "__main__": - from tkinter import Tk - - root = Tk() - root.withdraw() - root.title("Terminal") - - term = Terminal(root) - term.pack(expand=True, fill="both") - - root.update_idletasks() - - minimum_width: int = root.winfo_reqwidth() - minimum_height: int = root.winfo_reqheight() - - x_coords = int(root.winfo_screenwidth() / 2 - minimum_width / 2) - y_coords = int(root.wm_maxsize()[1] / 2 - minimum_height / 2) - - root.geometry(f"{minimum_width}x{minimum_height}+{x_coords}+{y_coords}") - root.wm_minsize(minimum_width, minimum_height) - - root.deiconify() - root.mainloop() From c50ac3519d3b5c0c6a4226762c749eb28d9b663c Mon Sep 17 00:00:00 2001 From: XiaoBaiYun <71159641+littlewhitecloud@users.noreply.github.com> Date: Wed, 12 Jul 2023 09:29:30 +0800 Subject: [PATCH 19/34] LOL REBUILD --- tktermwidget/__init__.py | 2 +- tktermwidget/widgets.py | 316 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 317 insertions(+), 1 deletion(-) create mode 100644 tktermwidget/widgets.py diff --git a/tktermwidget/__init__.py b/tktermwidget/__init__.py index 7fed88f..f00a52c 100644 --- a/tktermwidget/__init__.py +++ b/tktermwidget/__init__.py @@ -5,7 +5,7 @@ from platformdirs import user_cache_dir from .style import * # noqa: F401, F403 -from .tkterm import Terminal # noqa: F401 +from .widgets import Terminal # noqa: F401 # Get the package path PACKAGE_PATH = Path(user_cache_dir("tktermwidget")) diff --git a/tktermwidget/widgets.py b/tktermwidget/widgets.py new file mode 100644 index 0000000..099308a --- /dev/null +++ b/tktermwidget/widgets.py @@ -0,0 +1,316 @@ +"""Tkinter Terminal widget""" +from __future__ import annotations + +from os import chdir, getcwd +from pathlib import Path +from platform import system +from subprocess import PIPE, Popen +from tkinter import Event, Misc, Text +from tkinter.ttk import Frame, Scrollbar + +from platformdirs import user_cache_dir + +if __name__ == "__main__": from style import DEFAULT +else: from .style import DEFAULT + +HISTORY_PATH = Path(user_cache_dir("tktermwidget")) +HISTORY_FILE = HISTORY_PATH / "history.txt" +SYSTEM = system() +CREATE_NEWCONSOLE = 0 +SIGN = "$ " + +if SYSTEM == "Windows": # Check if platform is windows + from subprocess import CREATE_NEW_CONSOLE + + SIGN = ">" + +class AutoHideScrollbar(Scrollbar): + """Scrollbar that automatically hides when not needed""" + + def __init__(self, master=None, **kwargs): + Scrollbar.__init__(self, master=master, **kwargs) + + def set(self, first: int, last: int): + """Set the Scrollbar""" + if float(first) <= 0.0 and float(last) >= 1.0: + self.grid_remove() + else: + self.grid() + Scrollbar.set(self, first, last) + + +class Terminal(Frame): + """A terminal widget for tkinter applications + + Args: + master (Misc): The parent widget + style (dict, optional): Set the style for the Terminal widget + filehistory (str, optional): Set your own file history instead of the normal + autohide (bool, optional): Whether to autohide the scrollbars. Set true to enable. + *args: Arguments for the text widget + **kwargs: Keyword argu ments for the text widget + + Methods for outside use: + None + + Methods for internal use: + up (Event) -> str: Goes up in the history + down (Event) -> str: Goes down in the history + (If the user is at the bottom of the history, it clears the command) + left (Event) -> str: Goes left in the command if the index is greater than the directory + (So the user can't delete the directory or go left of it) + kill (Event) -> str: Kills the current command + loop (Event) -> str: Runs the command typed""" + + def __init__(self, master: Misc, style: dict = DEFAULT, filehistory: str = None, autohide: bool = False, *args, **kwargs): + Frame.__init__(self, master) + + # Set row and column weights + self.rowconfigure(0, weight=1) + self.columnconfigure(0, weight=1) + + # Create text widget and x, y scrollbar + self.style: dict = style + horizontal: bool = False + scrollbar = Scrollbar if not autohide else AutoHideScrollbar + + if kwargs.get("wrap", "char") == "none": + self.xscroll = scrollbar(self, orient="horizontal") + horizontal = True + + self.yscroll = scrollbar(self) + self.text = Text( + self, + *args, + wrap=kwargs.get("wrap", "char"), + yscrollcommand=self.yscroll.set, + relief=kwargs.get("relief", "flat"), + font=kwargs.get("font", ("Cascadia Code", 9, "normal")), + foreground=kwargs.get("foreground", self.style["foreground"]), + background=kwargs.get("background", self.style["background"]), + insertbackground=kwargs.get("insertbackground", self.style["insertbackground"]), + selectbackground=kwargs.get("selectbackground", self.style["selectbackground"]), + selectforeground=kwargs.get("selectforeground", self.style["selectforeground"]), + ) + + if horizontal: + self.text.config(xscrollcommand=self.xscroll.set) + self.xscroll.config(command=self.text.xview) + self.yscroll.config(command=self.text.yview) + + # Grid widgets + self.text.grid(row=0, column=0, sticky="nsew") + if horizontal: + self.xscroll.grid(row=1, column=0, sticky="ew") + self.yscroll.grid(row=0, column=1, sticky="ns") + + # Init command prompt + self.directory() + + # Set constants + self.longsymbol: str = "\\" if not SYSTEM == "Windows" else "&&" + self.filehistory: str = HISTORY_FILE if not filehistory else filehistory + + # Set variables + self.index: int = 1 + self.longcmd: str = "" + self.longflag: bool = False + self.current_process: Popen | None = None + self.cursor: int = self.text.index("insert") + + self.latest: int = self.cursor + + # Bind events + self.text.bind("", self.up, add=True) + self.text.bind("", self.down, add=True) + self.text.bind("", self.execute, add=True) + for bind_str in ("", ""): + self.text.bind(bind_str, self.left, add=True) + for bind_str in ("", "", "", ""): + self.text.bind(bind_str, self.check, add=True) + + self.text.bind("", self.kill, add=True) # Isn't working + + # History recorder + self.history = open( + self.filehistory, + "r+", # Both read and write + encoding="utf-8", + ) + + self.historys = [i.strip() for i in self.history.readlines()] + self.historyindex = len(self.historys) - 1 + + def check(self, _: Event) -> None: + """Update cursor and check if it is out of the edit range""" + self.cursor = self.text.index("insert") # Update cursor + if float(self.cursor) < float(self.latest): + for bind_str in ("", ""): + self.text.bind(bind_str, lambda _: "break", add=True) + else: + for unbind_str in ("", ""): + self.text.unbind(unbind_str) + + def directory(self) -> None: + """Insert the directory""" + self.text.insert( + "insert", + getcwd() + SIGN + ) + + def kill(self, _: Evnet) -> str: + """Kill the current process""" + if self.current_process: + self.current_process.terminate() + self.current_process = None + return "break" + + def execute(self, _: Event) -> str: + """Execute the command""" + # Get the line from the text + cmd = self.text.get(f"{self.index}.0", "end-1c") + # Split the command from the line also strip + cmd = cmd.split(SIGN)[-1].strip() + self.index += 1 # if some "if" statement return "break" + + if cmd.endswith(self.longsymbol): + self.longcmd += cmd.split(self.longsymbol)[0] + self.longflag = True + self.newline() + return "break" + + if self.longflag: + cmd = self.longcmd + cmd + self.longcmd = "" + self.longflag = False + + if cmd: # Record the command if it isn't empty + self.history.write(cmd + "\n") + self.historys.append(cmd) + self.historyindex = len(self.historys) - 1 + else: # Leave the loop + self.newline() + self.directory() + self.text.see("end") + return "break" + + # Check the command if it is a special command + if cmd in ["clear", "cls"]: + self.text.delete("1.0", "end") + self.directory() + return "break" + elif cmd == "exit": + self.master.quit() + + # Set the insert position is at the end + self.text.mark_set("insert", f"{self.index}.end") + self.text.see("insert") + + # TODO: Refactor the way we get output from subprocess + # Run the command + self.current_process = Popen( + cmd, + shell=True, + stdout=PIPE, + stderr=PIPE, + stdin=PIPE, + text=True, + cwd=getcwd(), # TODO: use dynamtic path instead (see #35) + creationflags=CREATE_NEW_CONSOLE, + ) + # The following needs to be put in an after so the kill command works + + # Check if the command was successful + output: tuple = self.current_process.communicate() + returnlines: str = output[0] + errors: str = output[1] + returncode = self.current_process.returncode + self.current_process = None + + if returncode != 0: + returnlines += errors # If the command was unsuccessful, it doesn't give stdout + # TODO: Get the success message from the command (see #16) + + # Output to the text + self.newline() + for line in returnlines: + self.text.insert("insert", line) + + # Update the text and the index + self.index = int(self.text.index("insert").split(".")[0]) + self.update() + return "break" # Prevent the default newline character insertion + + + def newline(self) -> None: + """Insert a newline""" + self.text.insert("insert", "\n") + + def update(self) -> str: + """Update the text widget or the command has no output""" + # Insert the directory + self.directory() + # Update cursor and check if it is out of the edit range + self.check(None) + # Update latest index + self.latest = self.text.index("insert") + # Warp to the end + self.text.see("end") + return "break" + + # Keypress + def down(self, _: Event) -> str: + """Go down in the history""" + if self.historyindex < len(self.historys) - 1: + self.text.delete(f"{self.index}.0", "end-1c") + self.directory() + # Insert the command + self.text.insert("insert", self.historys[self.historyindex]) + self.historyindex += 1 + else: + # Clear the command + self.text.delete(f"{self.index}.0", "end-1c") + self.directory() + return "break" + + def left(self, _: Event) -> str | None: + """Go left in the command if the command is greater than the path""" + insert_index = self.text.index("insert") + dir_index = f"{insert_index.split('.')[0]}.{len(getcwd() + SIGN)}" + if insert_index == dir_index: + return "break" + + def up(self, _: Event) -> str: + """Go up in the history""" + if self.historyindex >= 0: + self.text.delete(f"{self.index}.0", "end-1c") + self.directory() + # Insert the command + self.text.insert("insert", self.historys[self.historyindex]) + self.historyindex -= 1 + return "break" + + +if __name__ == "__main__": + from tkinter import Tk + + root = Tk() + root.withdraw() + root.title("Terminal") + + term = Terminal(root) + term.pack(expand=True, fill="both") + + root.update_idletasks() + + minimum_width: int = root.winfo_reqwidth() + minimum_height: int = root.winfo_reqheight() + + x_coords = int(root.winfo_screenwidth() / 2 - minimum_width / 2) + y_coords = int(root.wm_maxsize()[1] / 2 - minimum_height / 2) + + root.geometry(f"{minimum_width}x{minimum_height}+{x_coords}+{y_coords}") + root.wm_minsize(minimum_width, minimum_height) + + root.deiconify() + root.mainloop() From d46ddc6be746b4cacefba57d48fbebcb1e429fde Mon Sep 17 00:00:00 2001 From: XiaoBaiYun <71159641+littlewhitecloud@users.noreply.github.com> Date: Wed, 12 Jul 2023 09:29:52 +0800 Subject: [PATCH 20/34] good bye / --- tktermwidget/tkterm.py | 330 ----------------------------------------- 1 file changed, 330 deletions(-) delete mode 100644 tktermwidget/tkterm.py diff --git a/tktermwidget/tkterm.py b/tktermwidget/tkterm.py deleted file mode 100644 index 044407d..0000000 --- a/tktermwidget/tkterm.py +++ /dev/null @@ -1,330 +0,0 @@ -"""Terminal widget for tkinter""" -from __future__ import annotations - -from os import getcwd -from pathlib import Path -from platform import system -from subprocess import PIPE, Popen -from tkinter import Event, Misc, Text -from tkinter.ttk import Frame, Scrollbar - -from platformdirs import user_cache_dir - -if __name__ == "__main__": # Because it will fail when debugging on this file - from style import DEFAULT -else: # Call from another file - from .style import DEFAULT # noqa: F401 - -# Set constants -HISTORY_PATH = Path(user_cache_dir("tktermwidget")) -HISTORY_FILE = HISTORY_PATH / "history.txt" -SYSTEM = system() -CREATE_NEW_CONSOLE = 0 -SIGN = "$ " -if SYSTEM == "Windows": - from subprocess import CREATE_NEW_CONSOLE - - SIGN = ">" - - -class AutoHideScrollbar(Scrollbar): - """Scrollbar that automatically hides when not needed""" - - def __init__(self, master=None, **kwargs): - Scrollbar.__init__(self, master=master, **kwargs) - - def set(self, first: int, last: int): - """Set the Scrollbar""" - if float(first) <= 0.0 and float(last) >= 1.0: - self.grid_remove() - else: - self.grid() - Scrollbar.set(self, first, last) - - -class Terminal(Frame): - """A terminal widget for tkinter applications - - Args: - master (Misc): The parent widget - style (dict, optional): Set the style for the Terminal widget - filehistory (str, optional): Set your own file history instead of the normal - autohide (bool, optional): Whether to autohide the scrollbars. Set true to enable. - *args: Arguments for the text widget - **kwargs: Keyword arguments for the text widget - - Methods for outside use: - None - - Methods for internal use: - up (Event) -> str: Goes up in the history - down (Event) -> str: Goes down in the history - (If the user is at the bottom of the history, it clears the command) - left (Event) -> str: Goes left in the command if the index is greater than the directory - (So the user can't delete the directory or go left of it) - kill (Event) -> str: Kills the current command - loop (Event) -> str: Runs the command typed""" - - def __init__( - self, - master: Misc, - style: dict = DEFAULT, - filehistory: str = None, - autohide: bool = False, - *args, - **kwargs, - ): - Frame.__init__(self, master) - - # Set row and column weights - self.rowconfigure(0, weight=1) - self.columnconfigure(0, weight=1) - - # Create text widget and scrollbars - self.style = style - - scrollbars = Scrollbar if not autohide else AutoHideScrollbar - horizontal: bool = False - if kwargs.get("wrap", "char") == "none": - self.xscroll = scrollbars(self, orient="horizontal") - self.xscroll.grid(row=1, column=0, sticky="ew") - horizontal = True - - self.yscroll = scrollbars(self) - self.text = Text( - self, - *args, - background=kwargs.get("background", self.style["background"]), - insertbackground=kwargs.get("insertbackground", self.style["insertbackground"]), - selectbackground=kwargs.get("selectbackground", self.style["selectbackground"]), - selectforeground=kwargs.get("selectforeground", self.style["selectforeground"]), - relief=kwargs.get("relief", "flat"), - foreground=kwargs.get("foreground", self.style["foreground"]), - yscrollcommand=self.yscroll.set, - wrap=kwargs.get("wrap", "char"), - font=kwargs.get("font", ("Cascadia Code", 9, "normal")), - ) - if horizontal: - self.text.config(xscrollcommand=self.xscroll.set) - self.xscroll.config(command=self.text.xview) - - self.yscroll.config(command=self.text.yview) - - # Grid widgets - self.text.grid(row=0, column=0, sticky="nsew") - - self.yscroll.grid(row=0, column=1, sticky="ns") - - # self.yscroll.grid(row=1 if horizontal else 0, column=0 if horizontal else 1, sticky="ns") - # ^ - # TODO: this line should check again | tag - - # Create command prompt - self.directory() - - # Set constants - self.longsymbol: str = "\\" if not SYSTEM == "Windows" else "&&" - self.filehistory: str = HISTORY_FILE if not filehistory else filehistory - - # Set variables - self.index: int = 1 - self.longcmd: str = "" - self.longflag: bool = False - self.current_process: Popen | None = None - self.cursor: int = self.text.index("insert") - - self.latest: int = self.cursor # Init as the insert index, lazy to write again - - # Bind events - self.text.bind("", self.up, add=True) - self.text.bind("", self.down, add=True) - self.text.bind("", self.loop, add=True) - for bind_str in ("", ""): - self.text.bind(bind_str, self.left, add=True) - for bind_str in ("", ""): - self.text.bind(bind_str, self.check, add=True) - - self.text.bind("", self.kill, add=True) # Isn't working - - # History recorder - self.history = open( - self.filehistory, - "r+", - encoding="utf-8", - ) - - self.historys = [i.strip() for i in self.history.readlines() if i.strip()] - self.historyindex = len(self.historys) - 1 - - def check(self, _: Event) -> None: - """Update cursor""" - self.cursor = self.text.index("insert") - if float(self.cursor) < float(self.latest): - self.text.bind("", self.ignore, True) - self.text.bind("", self.ignore, True) - elif float(self.cursor) >= float(self.latest): - self.text.unbind("") - self.text.unbind("") - - def directory(self) -> None: - """Insert the directory""" - self.text.insert("insert", getcwd() + SIGN) - - def newline(self) -> None: - """Insert a newline""" - self.text.insert("insert", "\n") - - def ignore(self, _: Event) -> str: - """Ignore the event""" - return "break" - - def up(self, _: Event) -> str: - """Go up in the history""" - if self.historyindex >= 0: - self.text.delete(f"{self.index}.0", "end-1c") - self.directory() - # Insert the command - self.text.insert("insert", self.historys[self.historyindex].strip()) - self.historyindex -= 1 - return "break" - - def down(self, _: Event) -> str: - """Go down in the history""" - if self.historyindex < len(self.historys) - 1: - self.text.delete(f"{self.index}.0", "end-1c") - self.directory() - # Insert the command - self.text.insert("insert", self.historys[self.historyindex].strip()) - self.historyindex += 1 - else: - # Clear the command - self.text.delete(f"{self.index}.0", "end-1c") - self.directory() - return "break" - - def left(self, _: Event) -> str | None: - """Go left in the command if the command is greater than the path""" - insert_index = self.text.index("insert") - dir_index = f"{insert_index.split('.')[0]}.{len(getcwd() + SIGN)}" - if insert_index == dir_index: - return "break" - - def kill(self, _: Event) -> str: - """Kill the current process""" - if self.current_process: - self.current_process.kill() - self.current_process = None - return "break" - - def update(self) -> str: - """Update the text widget or the command has no output""" - self.directory() - self.check(None) - self.latest = self.text.index("insert") - self.text.see("end") - return "break" - - def loop(self, _: Event) -> str: - """Create an input loop""" - # Get the line from the text - cmd = self.text.get(f"{self.index}.0", "end-1c") - # Split the command from the line - cmd = cmd.split(SIGN)[-1].strip() - self.index += 1 # If return "break" - - if self.longflag: - self.longcmd += cmd - cmd = self.longcmd - self.longcmd = "" - self.longflag = False - - if cmd.endswith(self.longsymbol): - self.longcmd += cmd.split(self.longsymbol)[0] - self.longflag = True - self.newline() - return "break" - - if cmd: # Record the command if it isn't empty - self.history.write(cmd + "\n") - self.historys.append(cmd) - self.historyindex = len(self.historys) - 1 - else: # Leave the loop - self.newline() - self.directory() - self.text.see("end") - return "break" - - # Check the command if it is a special command - if cmd in ["clear", "cls"]: - self.text.delete("1.0", "end") - self.directory() - return "break" - elif cmd == "exit": - self.master.quit() - - # Check that the insert position is at the end - if self.text.index("insert") != f"{self.index}.end": - self.text.mark_set("insert", f"{self.index}.end") - self.text.see("insert") - - # TODO: Refactor the way we get output from subprocess - # Run the command - self.current_process = Popen( - cmd, - shell=True, - stdout=PIPE, - stderr=PIPE, - stdin=PIPE, - text=True, - bufsize=1, - universal_newlines=True, - cwd=getcwd(), # TODO: use dynamtic path instead (see #35) - creationflags=CREATE_NEW_CONSOLE, - ) - # The following needs to be put in an after so the kill command works - - # Check if the command was successful - output: tuple = self.current_process.communicate() - returnlines: str = output[0] - errors: str = output[1] - returncode = self.current_process.returncode - self.current_process = None - - if returncode != 0: - returnlines += errors # If the command was unsuccessful, it doesn't give stdout - # TODO: Get the success message from the command (see #16) - - # Output to the text - self.newline() - for line in returnlines: - self.text.insert("insert", line) - - # Update the text and the index - self.index = int(self.text.index("insert").split(".")[0]) - self.update() - return "break" # Prevent the default newline character insertion - - -if __name__ == "__main__": - from tkinter import Tk - - root = Tk() - root.withdraw() - root.title("Terminal") - - term = Terminal(root) - term.pack(expand=True, fill="both") - - root.update_idletasks() - - minimum_width: int = root.winfo_reqwidth() - minimum_height: int = root.winfo_reqheight() - - x_coords = int(root.winfo_screenwidth() / 2 - minimum_width / 2) - y_coords = int(root.wm_maxsize()[1] / 2 - minimum_height / 2) - - root.geometry(f"{minimum_width}x{minimum_height}+{x_coords}+{y_coords}") - root.wm_minsize(minimum_width, minimum_height) - - root.deiconify() - root.mainloop() From 76c5fe671022bdd6772196a8b16a6a840712a2b9 Mon Sep 17 00:00:00 2001 From: XiaoBaiYun <71159641+littlewhitecloud@users.noreply.github.com> Date: Wed, 12 Jul 2023 09:31:49 +0800 Subject: [PATCH 21/34] black, isort, ruff check . --fix! --- tktermwidget/widgets.py | 80 ++++++++++++++++++++++------------------- 1 file changed, 43 insertions(+), 37 deletions(-) diff --git a/tktermwidget/widgets.py b/tktermwidget/widgets.py index 099308a..123431c 100644 --- a/tktermwidget/widgets.py +++ b/tktermwidget/widgets.py @@ -1,7 +1,7 @@ """Tkinter Terminal widget""" from __future__ import annotations -from os import chdir, getcwd +from os import getcwd from pathlib import Path from platform import system from subprocess import PIPE, Popen @@ -10,8 +10,10 @@ from platformdirs import user_cache_dir -if __name__ == "__main__": from style import DEFAULT -else: from .style import DEFAULT +if __name__ == "__main__": + from style import DEFAULT +else: + from .style import DEFAULT HISTORY_PATH = Path(user_cache_dir("tktermwidget")) HISTORY_FILE = HISTORY_PATH / "history.txt" @@ -19,11 +21,12 @@ CREATE_NEWCONSOLE = 0 SIGN = "$ " -if SYSTEM == "Windows": # Check if platform is windows +if SYSTEM == "Windows": # Check if platform is windows from subprocess import CREATE_NEW_CONSOLE SIGN = ">" + class AutoHideScrollbar(Scrollbar): """Scrollbar that automatically hides when not needed""" @@ -62,13 +65,20 @@ class Terminal(Frame): kill (Event) -> str: Kills the current command loop (Event) -> str: Runs the command typed""" - def __init__(self, master: Misc, style: dict = DEFAULT, filehistory: str = None, autohide: bool = False, *args, **kwargs): + def __init__( + self, master: Misc, + style: dict = DEFAULT, + filehistory: str = None, + autohide: bool = False, + *args, + **kwargs + ): Frame.__init__(self, master) - - # Set row and column weights + + # Set row and column weights self.rowconfigure(0, weight=1) self.columnconfigure(0, weight=1) - + # Create text widget and x, y scrollbar self.style: dict = style horizontal: bool = False @@ -92,18 +102,18 @@ def __init__(self, master: Misc, style: dict = DEFAULT, filehistory: str = None, selectbackground=kwargs.get("selectbackground", self.style["selectbackground"]), selectforeground=kwargs.get("selectforeground", self.style["selectforeground"]), ) - + if horizontal: self.text.config(xscrollcommand=self.xscroll.set) self.xscroll.config(command=self.text.xview) self.yscroll.config(command=self.text.yview) - + # Grid widgets self.text.grid(row=0, column=0, sticky="nsew") if horizontal: self.xscroll.grid(row=1, column=0, sticky="ew") self.yscroll.grid(row=0, column=1, sticky="ns") - + # Init command prompt self.directory() @@ -134,56 +144,53 @@ def __init__(self, master: Misc, style: dict = DEFAULT, filehistory: str = None, # History recorder self.history = open( self.filehistory, - "r+", # Both read and write + "r+", # Both read and write encoding="utf-8", ) self.historys = [i.strip() for i in self.history.readlines()] self.historyindex = len(self.historys) - 1 - + def check(self, _: Event) -> None: """Update cursor and check if it is out of the edit range""" - self.cursor = self.text.index("insert") # Update cursor + self.cursor = self.text.index("insert") # Update cursor if float(self.cursor) < float(self.latest): for bind_str in ("", ""): self.text.bind(bind_str, lambda _: "break", add=True) else: for unbind_str in ("", ""): self.text.unbind(unbind_str) - + def directory(self) -> None: """Insert the directory""" - self.text.insert( - "insert", - getcwd() + SIGN - ) - - def kill(self, _: Evnet) -> str: + self.text.insert("insert", getcwd() + SIGN) + + def kill(self, _: Event) -> str: """Kill the current process""" if self.current_process: self.current_process.terminate() self.current_process = None return "break" - + def execute(self, _: Event) -> str: """Execute the command""" # Get the line from the text cmd = self.text.get(f"{self.index}.0", "end-1c") # Split the command from the line also strip cmd = cmd.split(SIGN)[-1].strip() - self.index += 1 # if some "if" statement return "break" - + self.index += 1 # if some "if" statement return "break" + if cmd.endswith(self.longsymbol): self.longcmd += cmd.split(self.longsymbol)[0] self.longflag = True self.newline() return "break" - + if self.longflag: cmd = self.longcmd + cmd self.longcmd = "" self.longflag = False - + if cmd: # Record the command if it isn't empty self.history.write(cmd + "\n") self.historys.append(cmd) @@ -201,11 +208,11 @@ def execute(self, _: Event) -> str: return "break" elif cmd == "exit": self.master.quit() - - # Set the insert position is at the end + + # Set the insert position is at the end self.text.mark_set("insert", f"{self.index}.end") self.text.see("insert") - + # TODO: Refactor the way we get output from subprocess # Run the command self.current_process = Popen( @@ -219,14 +226,14 @@ def execute(self, _: Event) -> str: creationflags=CREATE_NEW_CONSOLE, ) # The following needs to be put in an after so the kill command works - + # Check if the command was successful output: tuple = self.current_process.communicate() returnlines: str = output[0] errors: str = output[1] returncode = self.current_process.returncode self.current_process = None - + if returncode != 0: returnlines += errors # If the command was unsuccessful, it doesn't give stdout # TODO: Get the success message from the command (see #16) @@ -241,7 +248,6 @@ def execute(self, _: Event) -> str: self.update() return "break" # Prevent the default newline character insertion - def newline(self) -> None: """Insert a newline""" self.text.insert("insert", "\n") @@ -258,7 +264,7 @@ def update(self) -> str: self.text.see("end") return "break" - # Keypress + # Keypress def down(self, _: Event) -> str: """Go down in the history""" if self.historyindex < len(self.historys) - 1: @@ -272,14 +278,14 @@ def down(self, _: Event) -> str: self.text.delete(f"{self.index}.0", "end-1c") self.directory() return "break" - + def left(self, _: Event) -> str | None: """Go left in the command if the command is greater than the path""" insert_index = self.text.index("insert") dir_index = f"{insert_index.split('.')[0]}.{len(getcwd() + SIGN)}" if insert_index == dir_index: return "break" - + def up(self, _: Event) -> str: """Go up in the history""" if self.historyindex >= 0: @@ -289,8 +295,8 @@ def up(self, _: Event) -> str: self.text.insert("insert", self.historys[self.historyindex]) self.historyindex -= 1 return "break" - - + + if __name__ == "__main__": from tkinter import Tk From 21a4ac24d5d87e23304398617a3a243d09f63a85 Mon Sep 17 00:00:00 2001 From: XiaoBaiYun <71159641+littlewhitecloud@users.noreply.github.com> Date: Wed, 12 Jul 2023 09:34:37 +0800 Subject: [PATCH 22/34] document? --- tktermwidget/widgets.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tktermwidget/widgets.py b/tktermwidget/widgets.py index 123431c..5c494a5 100644 --- a/tktermwidget/widgets.py +++ b/tktermwidget/widgets.py @@ -63,7 +63,8 @@ class Terminal(Frame): left (Event) -> str: Goes left in the command if the index is greater than the directory (So the user can't delete the directory or go left of it) kill (Event) -> str: Kills the current command - loop (Event) -> str: Runs the command typed""" + check (Event) -> None: Update cursor and check it if is out of the edit range + execute(Event) -> str: Execute the command""" def __init__( self, master: Misc, From 1f187c37adf779c68506634abd9de394701a82c8 Mon Sep 17 00:00:00 2001 From: XiaoBaiYun <71159641+littlewhitecloud@users.noreply.github.com> Date: Wed, 12 Jul 2023 09:48:03 +0800 Subject: [PATCH 23/34] Update widgets.py --- tktermwidget/widgets.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tktermwidget/widgets.py b/tktermwidget/widgets.py index 5c494a5..a1bc9b6 100644 --- a/tktermwidget/widgets.py +++ b/tktermwidget/widgets.py @@ -67,11 +67,11 @@ class Terminal(Frame): execute(Event) -> str: Execute the command""" def __init__( - self, master: Misc, - style: dict = DEFAULT, - filehistory: str = None, - autohide: bool = False, - *args, + self, master: Misc, + style: dict = DEFAULT, + filehistory: str = None, + autohide: bool = False, + *args, **kwargs ): Frame.__init__(self, master) From e38f1a459187036047cad763f87e5dd02cc12d91 Mon Sep 17 00:00:00 2001 From: XiaoBaiYun <71159641+littlewhitecloud@users.noreply.github.com> Date: Wed, 12 Jul 2023 17:37:15 +0800 Subject: [PATCH 24/34] fix #35 --- tktermwidget/widgets.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/tktermwidget/widgets.py b/tktermwidget/widgets.py index a1bc9b6..58b62cf 100644 --- a/tktermwidget/widgets.py +++ b/tktermwidget/widgets.py @@ -1,7 +1,7 @@ """Tkinter Terminal widget""" from __future__ import annotations -from os import getcwd +from os import getcwd, chdir, path from pathlib import Path from platform import system from subprocess import PIPE, Popen @@ -179,7 +179,9 @@ def execute(self, _: Event) -> str: cmd = self.text.get(f"{self.index}.0", "end-1c") # Split the command from the line also strip cmd = cmd.split(SIGN)[-1].strip() - self.index += 1 # if some "if" statement return "break" + + # TODO: get rid off all return "break" in if statements + # use the flag leave: bool instead of if cmd.endswith(self.longsymbol): self.longcmd += cmd.split(self.longsymbol)[0] @@ -209,6 +211,15 @@ def execute(self, _: Event) -> str: return "break" elif cmd == "exit": self.master.quit() + elif cmd.startswith("cd"): # TAG: is all platform use cd...? + if cmd == "cd..": + chdir(path.abspath(path.join(getcwd(), ".."))) + else: + chdir(cmd.split()[-1]) + self.newline() + self.directory() + return "break" + # Set the insert position is at the end self.text.mark_set("insert", f"{self.index}.end") @@ -229,9 +240,9 @@ def execute(self, _: Event) -> str: # The following needs to be put in an after so the kill command works # Check if the command was successful - output: tuple = self.current_process.communicate() - returnlines: str = output[0] - errors: str = output[1] + returnlines: str = "" + errors: str = "" + returnlines, errors = self.current_process.communicate() returncode = self.current_process.returncode self.current_process = None @@ -247,11 +258,15 @@ def execute(self, _: Event) -> str: # Update the text and the index self.index = int(self.text.index("insert").split(".")[0]) self.update() + print(self.index) + + del returnlines, errors, returncode, cmd return "break" # Prevent the default newline character insertion def newline(self) -> None: """Insert a newline""" self.text.insert("insert", "\n") + self.index += 1 def update(self) -> str: """Update the text widget or the command has no output""" From 5c2d8db644ea7d4039ab41b97b63cd619d5602dc Mon Sep 17 00:00:00 2001 From: XiaoBaiYun <71159641+littlewhitecloud@users.noreply.github.com> Date: Wed, 12 Jul 2023 17:39:00 +0800 Subject: [PATCH 25/34] black isort ruff check --- tktermwidget/style.py | 2 +- tktermwidget/widgets.py | 20 +++++++------------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/tktermwidget/style.py b/tktermwidget/style.py index d916678..baab7a5 100644 --- a/tktermwidget/style.py +++ b/tktermwidget/style.py @@ -89,7 +89,7 @@ def __init__(self, usetheme: bool = False, basedon: dict[str] = DEFAULT): from sv_ttk import set_theme set_theme("dark" if isDark() else "light") - self.option_add("*font", ("Cascadia Mono", 9)) + self.option_add("*font", ("Consolas", 9)) if isDark(): from ctypes import byref, c_int, sizeof, windll diff --git a/tktermwidget/widgets.py b/tktermwidget/widgets.py index 58b62cf..b6eb98d 100644 --- a/tktermwidget/widgets.py +++ b/tktermwidget/widgets.py @@ -1,7 +1,7 @@ """Tkinter Terminal widget""" from __future__ import annotations -from os import getcwd, chdir, path +from os import chdir, getcwd, path from pathlib import Path from platform import system from subprocess import PIPE, Popen @@ -67,12 +67,7 @@ class Terminal(Frame): execute(Event) -> str: Execute the command""" def __init__( - self, master: Misc, - style: dict = DEFAULT, - filehistory: str = None, - autohide: bool = False, - *args, - **kwargs + self, master: Misc, style: dict = DEFAULT, filehistory: str = None, autohide: bool = False, *args, **kwargs ): Frame.__init__(self, master) @@ -180,8 +175,8 @@ def execute(self, _: Event) -> str: # Split the command from the line also strip cmd = cmd.split(SIGN)[-1].strip() - # TODO: get rid off all return "break" in if statements - # use the flag leave: bool instead of + # TODO: get rid off all return "break" in if statements + # use the flag leave: bool instead of if cmd.endswith(self.longsymbol): self.longcmd += cmd.split(self.longsymbol)[0] @@ -211,7 +206,7 @@ def execute(self, _: Event) -> str: return "break" elif cmd == "exit": self.master.quit() - elif cmd.startswith("cd"): # TAG: is all platform use cd...? + elif cmd.startswith("cd"): # TAG: is all platform use cd...? if cmd == "cd..": chdir(path.abspath(path.join(getcwd(), ".."))) else: @@ -219,7 +214,6 @@ def execute(self, _: Event) -> str: self.newline() self.directory() return "break" - # Set the insert position is at the end self.text.mark_set("insert", f"{self.index}.end") @@ -259,8 +253,8 @@ def execute(self, _: Event) -> str: self.index = int(self.text.index("insert").split(".")[0]) self.update() print(self.index) - - del returnlines, errors, returncode, cmd + + del returnlines, errors, returncode, cmd return "break" # Prevent the default newline character insertion def newline(self) -> None: From aedd22f440a6352375adbc938afec77ec59d405a Mon Sep 17 00:00:00 2001 From: XiaoBaiYun <71159641+littlewhitecloud@users.noreply.github.com> Date: Wed, 12 Jul 2023 17:51:41 +0800 Subject: [PATCH 26/34] o, f --- tktermwidget/style.py | 2 +- tktermwidget/widgets.py | 27 +++++++++++++++------------ 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/tktermwidget/style.py b/tktermwidget/style.py index baab7a5..d916678 100644 --- a/tktermwidget/style.py +++ b/tktermwidget/style.py @@ -89,7 +89,7 @@ def __init__(self, usetheme: bool = False, basedon: dict[str] = DEFAULT): from sv_ttk import set_theme set_theme("dark" if isDark() else "light") - self.option_add("*font", ("Consolas", 9)) + self.option_add("*font", ("Cascadia Mono", 9)) if isDark(): from ctypes import byref, c_int, sizeof, windll diff --git a/tktermwidget/widgets.py b/tktermwidget/widgets.py index b6eb98d..cc41f2a 100644 --- a/tktermwidget/widgets.py +++ b/tktermwidget/widgets.py @@ -10,7 +10,7 @@ from platformdirs import user_cache_dir -if __name__ == "__main__": +if __name__ == "__main__": # For develop from style import DEFAULT else: from .style import DEFAULT @@ -51,7 +51,7 @@ class Terminal(Frame): filehistory (str, optional): Set your own file history instead of the normal autohide (bool, optional): Whether to autohide the scrollbars. Set true to enable. *args: Arguments for the text widget - **kwargs: Keyword argu ments for the text widget + **kwargs: Keyword arguments for the text widget Methods for outside use: None @@ -64,7 +64,7 @@ class Terminal(Frame): (So the user can't delete the directory or go left of it) kill (Event) -> str: Kills the current command check (Event) -> None: Update cursor and check it if is out of the edit range - execute(Event) -> str: Execute the command""" + execute (Event) -> str: Execute the command""" def __init__( self, master: Misc, style: dict = DEFAULT, filehistory: str = None, autohide: bool = False, *args, **kwargs @@ -126,6 +126,16 @@ def __init__( self.latest: int = self.cursor + # History recorder + self.history = open( + self.filehistory, + "r+", # Both read and write + encoding="utf-8", + ) + + self.historys = [i.strip() for i in self.history.readlines()] + self.historyindex = len(self.historys) - 1 + # Bind events self.text.bind("", self.up, add=True) self.text.bind("", self.down, add=True) @@ -137,15 +147,7 @@ def __init__( self.text.bind("", self.kill, add=True) # Isn't working - # History recorder - self.history = open( - self.filehistory, - "r+", # Both read and write - encoding="utf-8", - ) - - self.historys = [i.strip() for i in self.history.readlines()] - self.historyindex = len(self.historys) - 1 + del horizontal def check(self, _: Event) -> None: """Update cursor and check if it is out of the edit range""" @@ -294,6 +296,7 @@ def left(self, _: Event) -> str | None: insert_index = self.text.index("insert") dir_index = f"{insert_index.split('.')[0]}.{len(getcwd() + SIGN)}" if insert_index == dir_index: + del insert_index, dir_index return "break" def up(self, _: Event) -> str: From 7258fdd10024672d573c23700acaefb7b508c3fb Mon Sep 17 00:00:00 2001 From: XiaoBaiYun <71159641+littlewhitecloud@users.noreply.github.com> Date: Sun, 16 Jul 2023 08:12:26 +0800 Subject: [PATCH 27/34] Update __init__.py --- tktermwidget/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tktermwidget/__init__.py b/tktermwidget/__init__.py index f00a52c..a4a5204 100644 --- a/tktermwidget/__init__.py +++ b/tktermwidget/__init__.py @@ -4,9 +4,6 @@ from platformdirs import user_cache_dir -from .style import * # noqa: F401, F403 -from .widgets import Terminal # noqa: F401 - # Get the package path PACKAGE_PATH = Path(user_cache_dir("tktermwidget")) # Get the history file @@ -32,3 +29,6 @@ if not (JSON_FILE).exists(): with open(JSON_FILE, "w", encoding="utf-8") as f: dump("{}", f) + +from .style import * # noqa: F401, F403 +from .widgets import Terminal # noqa: F401 From d21f184ee11a4cbdb8dc204f1cc45ff699b6e429 Mon Sep 17 00:00:00 2001 From: XiaoBaiYun <71159641+littlewhitecloud@users.noreply.github.com> Date: Mon, 17 Jul 2023 09:54:50 +0800 Subject: [PATCH 28/34] move checks to the utils and some small tweaks Add E402 for `ruff check .` (Really hate it) --- tktermwidget/__init__.py | 38 ++++++-------------------------------- tktermwidget/utils.py | 35 +++++++++++++++++++++++++++++++++++ tktermwidget/widgets.py | 9 +++------ 3 files changed, 44 insertions(+), 38 deletions(-) create mode 100644 tktermwidget/utils.py diff --git a/tktermwidget/__init__.py b/tktermwidget/__init__.py index a4a5204..418da7b 100644 --- a/tktermwidget/__init__.py +++ b/tktermwidget/__init__.py @@ -1,34 +1,8 @@ -"""__init__ of the tktermwidget package""" -from json import dump -from pathlib import Path +"""Import tktermwidget package""" +import utils -from platformdirs import user_cache_dir +utils.check() # Check the files -# Get the package path -PACKAGE_PATH = Path(user_cache_dir("tktermwidget")) -# Get the history file -HISTORY_FILE = PACKAGE_PATH / "history.txt" -# Get the json file (style) -JSON_FILE = PACKAGE_PATH / "styles.json" - -if not PACKAGE_PATH.exists(): # Check the "tktermwidget" is exsit - PACKAGE_PATH.mkdir(parents=True) - # Also create the history file - with open(HISTORY_FILE, "w", encoding="utf-8") as f: - f.close() - # Also create the json file - with open(JSON_FILE, "w", encoding="utf-8") as f: - dump("{}", f) - -# Check that the history file exists -if not (HISTORY_FILE).exists(): - with open(HISTORY_FILE, "w", encoding="utf-8") as f: - f.close() - -# Check that the json file exists -if not (JSON_FILE).exists(): - with open(JSON_FILE, "w", encoding="utf-8") as f: - dump("{}", f) - -from .style import * # noqa: F401, F403 -from .widgets import Terminal # noqa: F401 +# Import them after the check +from .style import * # noqa: F401, F403, E402 +from .widgets import Terminal # noqa: F401, E402 diff --git a/tktermwidget/utils.py b/tktermwidget/utils.py new file mode 100644 index 0000000..82b6296 --- /dev/null +++ b/tktermwidget/utils.py @@ -0,0 +1,35 @@ +"""Some useful tools""" + + +def check(): + """Check files and create them if they don't exsit""" + from json import dump + from pathlib import Path + + from platformdirs import user_cache_dir + + # Get the package path + PACKAGE_PATH = Path(user_cache_dir("tktermwidget")) + # Get the history file + HISTORY_FILE = PACKAGE_PATH / "history.txt" + # Get the json file (style) + JSON_FILE = PACKAGE_PATH / "styles.json" + + if not PACKAGE_PATH.exists(): # Check the "tktermwidget" is exsit + PACKAGE_PATH.mkdir(parents=True) + # Also create the history file + with open(HISTORY_FILE, "w", encoding="utf-8") as f: + f.close() + # Also create the json file + with open(JSON_FILE, "w", encoding="utf-8") as f: + dump("{}", f) + + # Check that the history file exists + if not (HISTORY_FILE).exists(): + with open(HISTORY_FILE, "w", encoding="utf-8") as f: + f.close() + + # Check that the json file exists + if not (JSON_FILE).exists(): + with open(JSON_FILE, "w", encoding="utf-8") as f: + dump("{}", f) diff --git a/tktermwidget/widgets.py b/tktermwidget/widgets.py index cc41f2a..dd6bbe4 100644 --- a/tktermwidget/widgets.py +++ b/tktermwidget/widgets.py @@ -173,13 +173,11 @@ def kill(self, _: Event) -> str: def execute(self, _: Event) -> str: """Execute the command""" # Get the line from the text - cmd = self.text.get(f"{self.index}.0", "end-1c") + cmd: str = self.text.get(f"{self.index}.0", "end-1c") # Split the command from the line also strip cmd = cmd.split(SIGN)[-1].strip() - # TODO: get rid off all return "break" in if statements - # use the flag leave: bool instead of - + # Special check if cmd.endswith(self.longsymbol): self.longcmd += cmd.split(self.longsymbol)[0] self.longflag = True @@ -254,9 +252,8 @@ def execute(self, _: Event) -> str: # Update the text and the index self.index = int(self.text.index("insert").split(".")[0]) self.update() - print(self.index) - del returnlines, errors, returncode, cmd + del cmd, errors, returncode, returnlines return "break" # Prevent the default newline character insertion def newline(self) -> None: From 6815dabbb2b1b88cb289d5df0320519915e9a1dc Mon Sep 17 00:00:00 2001 From: XiaoBaiYun <71159641+littlewhitecloud@users.noreply.github.com> Date: Mon, 17 Jul 2023 11:15:46 +0800 Subject: [PATCH 29/34] nvm --- tktermwidget/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tktermwidget/__init__.py b/tktermwidget/__init__.py index 418da7b..43eef8a 100644 --- a/tktermwidget/__init__.py +++ b/tktermwidget/__init__.py @@ -1,7 +1,7 @@ """Import tktermwidget package""" -import utils +from .utils import check -utils.check() # Check the files +check() # Check the files # Import them after the check from .style import * # noqa: F401, F403, E402 From d35f1b4da188a913977321c368b4df8cbe049f13 Mon Sep 17 00:00:00 2001 From: XiaoBaiYun <71159641+littlewhitecloud@users.noreply.github.com> Date: Mon, 17 Jul 2023 15:13:18 +0800 Subject: [PATCH 30/34] Update style.py --- tktermwidget/style.py | 129 ++++++++++++++++++++++++------------------ 1 file changed, 73 insertions(+), 56 deletions(-) diff --git a/tktermwidget/style.py b/tktermwidget/style.py index d916678..3fc14a5 100644 --- a/tktermwidget/style.py +++ b/tktermwidget/style.py @@ -1,4 +1,4 @@ -"""Styles for terminal widget""" +"""Write or config styles for terminal widget""" from __future__ import annotations from json import dump, load @@ -14,14 +14,16 @@ STYLE_PATH = Path(user_cache_dir("tktermwidget")) JSON_FILE = STYLE_PATH / "styles.json" -# Styles format: -# {yourstylename}: dict[str] = { -# "background": "{yourhexcolor}", -# "insertbackground": "{yourhexcolor}", -# "selectbackground": "{yourhexcolor}", -# "selectforeground": "{yourhexcolor}", -# "foreground": "{yourhexcolor}", -# } +""" +# A style example +{stylename}: dict[str] = { # Style for {...} + "background": "{hexcolor}", + "insertbackground": "{hexcolor}", + "selectbackground": "{hexcolor}", + "selectforeground": "{hexcolor}", + "foreground": "{hexcolor}", +} +""" # Built-in styles DEFAULT: dict[str] = { # Style for normal tkterminalwidget @@ -57,7 +59,6 @@ } -# Functions def write_style(**styles) -> None: """Write the style into the json file""" # User can call this function to write the style without gui @@ -73,37 +74,50 @@ def load_style() -> dict: return load(f) -# Class class Config(Tk): - """ "A config gui for user to edit their custom styles""" - - def __init__(self, usetheme: bool = False, basedon: dict[str] = DEFAULT): + """A config gui for user to edit their custom styles + + Args: + edit (bool): Enable the edit the text in the render + basedon (dict): Create a style based on the style you choose + usetheme (bool): Enable the apply sv_ttk theme to the window + """ + + def __init__(self, edit: bool = False, basedon: dict[str] = DEFAULT, usetheme: bool = False): super().__init__() + + # Setup window self.geometry("855x525") self.title("Config your custom style") self.resizable(False, False) - self.iconbitmap("") # Must call this function or we can't get the hwnd + self.iconbitmap("") + # Apply sv_ttk theme to the window if usetheme: - from darkdetect import isDark - from sv_ttk import set_theme - - set_theme("dark" if isDark() else "light") - self.option_add("*font", ("Cascadia Mono", 9)) - - if isDark(): - from ctypes import byref, c_int, sizeof, windll - - windll.dwmapi.DwmSetWindowAttribute( - windll.user32.GetParent(self.winfo_id()), 20, byref(c_int(2)), sizeof(c_int(2)) - ) - self.withdraw() - self.deiconify() - + try: + from darkdetect import isDark + from sv_ttk import set_theme + except: + usetheme = False + else: + set_theme("dark" if isDark() else "light") + self.option_add("*font", ("Cascadia Mono", 9)) + # Enable window's darkmode + if isDark(): + from ctypes import byref, c_int, sizeof, windll + + windll.dwmapi.DwmSetWindowAttribute( + windll.user32.GetParent(self.winfo_id()), 20, byref(c_int(2)), sizeof(c_int(2)) + ) + self.withdraw() + self.deiconify() + + # If basedon is not DEFAULT then use basedon + # If basedon is DEFAULT then use load_style() + # If load_style() return a empty dict then use DEFAULT self.style: dict[str] = basedon if basedon != DEFAULT else load_style() if load_style() != {} else DEFAULT # Color choose or input widgets - # TODO: check the hex color is it vaild buttonframe = Frame(self) save = Button(buttonframe, text="Save", width=6, command=self.savestyle) cancel = Button(buttonframe, text="Cancel", width=6, command=self.destroy) @@ -113,7 +127,8 @@ def __init__(self, usetheme: bool = False, basedon: dict[str] = DEFAULT): background = Label(backgroundframe, text="Choose or input your normalbackground hex color") backgroundentry = Entry(backgroundframe) backgroundbutton = Button(backgroundframe, command=lambda: self.selectcolor(backgroundentry, "background")) - + + # TODO: improve all the labels' text insertbackgroundframe = Frame(self) insertbackground = Label(insertbackgroundframe, text="Choose or input your insertbackground hex color") insertbackgroundentry = Entry(insertbackgroundframe) @@ -143,27 +158,28 @@ def __init__(self, usetheme: bool = False, basedon: dict[str] = DEFAULT): foregroundentry = Entry(foregroundframe) foregroundbutton = Button(foregroundframe, command=lambda: self.selectcolor(foregroundentry, "foreground")) - # Style render configs + # Config style render self.render = Text( self, width=40, + relief="flat", + font=("Cascadia Mono", 9, "normal"), + foreground=self.style["foreground"], background=self.style["background"], insertbackground=self.style["insertbackground"], selectbackground=self.style["selectbackground"], selectforeground=self.style["selectforeground"], - foreground=self.style["foreground"], - font=("Cascadia Mono", 9, "normal"), - relief="flat", ) - + + # Write down some example text self.render.insert("insert", "This is a normal text for test style.") - self.render.tag_add("select", "1.31", "1.36") + self.render.tag_add("select", "1.28", "1.39") self.render.tag_config( "select", background=self.style["selectbackground"], foreground=self.style["selectforeground"] ) - self.render["state"] = "disable" - - # add the theme to the button widgets if usetheme == True + self.render["state"] = "normal" if edit else "disable" + + # Apply the theme to the button widgets if usetheme is True if usetheme: for widget in ( backgroundbutton, @@ -175,13 +191,20 @@ def __init__(self, usetheme: bool = False, basedon: dict[str] = DEFAULT): widget.config(style="Accent.TButton", width=2, text="🎨") save.config(style="Accent.TButton") - # fill the entry with hexcolor before pack + # Fill the entry with hexcolor for widget, hexcolor in zip( (backgroundentry, insertbackgroundentry, selectbackgroundentry, selectforegroundentry, foregroundentry), self.style.values(), ): widget.insert("insert", hexcolor) - + + # Bind some events + backgroundentry.bind("", lambda event: self.checkhexcolor(event, "background")) + insertbackgroundentry.bind("", lambda event: self.checkhexcolor(event, "insertbackground")) + selectbackgroundentry.bind("", lambda event: self.checkhexcolor(event, "selectbackground")) + selectforegroundentry.bind("", lambda event: self.checkhexcolor(event, "selectforeground")) + foregroundentry.bind("", lambda event: self.checkhexcolor(event, "foreground")) + # Pack the widgets cancel.pack(side="right", padx=1) save.pack(side="right", padx=3) @@ -209,12 +232,6 @@ def __init__(self, usetheme: bool = False, basedon: dict[str] = DEFAULT): ): widget.pack(side="left", padx=3) - backgroundentry.bind("", lambda event: self.checkhexcolor(event, "background")) - insertbackgroundentry.bind("", lambda event: self.checkhexcolor(event, "insertbackground")) - selectbackgroundentry.bind("", lambda event: self.checkhexcolor(event, "selectbackground")) - selectforegroundentry.bind("", lambda event: self.checkhexcolor(event, "selectforeground")) - foregroundentry.bind("", lambda event: self.checkhexcolor(event, "foreground")) - for widget in ( backgroundframe, insertbackgroundframe, @@ -234,7 +251,7 @@ def selectcolor(self, entry: Entry, name: str) -> None: self.updaterender() # update the render to show the latest style def updaterender(self) -> None: - """Let the render show with the latest style""" + """Let the render widget render with the latest style""" self.render.config( background=self.style["background"], insertbackground=self.style["insertbackground"], @@ -248,7 +265,7 @@ def updaterender(self) -> None: self.update() def savestyle(self) -> None: - """Save the style""" + """Save the style to the json file""" write_style( background=self.style["background"], insertbackground=self.style["insertbackground"], @@ -259,7 +276,7 @@ def savestyle(self) -> None: self.destroy() def checkhexcolor(self, event: Event, name: str) -> None: - """Check the hex color""" + """Check the hex color is vaild""" if match(r"^#(?:[0-9a-fA-F]{3}){1,2}$", event.widget.get()): event.widget.state(["invalid"]) self.style[name] = event.widget.get() @@ -267,10 +284,10 @@ def checkhexcolor(self, event: Event, name: str) -> None: else: event.widget.state(["!invalid"]) - -CUSTOM: dict[str] = load_style() +# Load the custom style +CUSTOM: dict[str] = load_style() if load_style() != {} else DEFAULT if __name__ == "__main__": - # An example or a test - configstyle = Config(True, basedon=POWERSHELL) + # An example basedon "powershell" style and also use sv_ttk theme + configstyle = Config(edit = True, basedon=POWERSHELL, usetheme = True) configstyle.mainloop() From fc616ed0be887605784e996ba6883a9a12406876 Mon Sep 17 00:00:00 2001 From: XiaoBaiYun <71159641+littlewhitecloud@users.noreply.github.com> Date: Mon, 17 Jul 2023 15:16:09 +0800 Subject: [PATCH 31/34] black isort ruff check and small tweaks --- tktermwidget/style.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/tktermwidget/style.py b/tktermwidget/style.py index 3fc14a5..93b4c29 100644 --- a/tktermwidget/style.py +++ b/tktermwidget/style.py @@ -76,7 +76,7 @@ def load_style() -> dict: class Config(Tk): """A config gui for user to edit their custom styles - + Args: edit (bool): Enable the edit the text in the render basedon (dict): Create a style based on the style you choose @@ -94,14 +94,15 @@ def __init__(self, edit: bool = False, basedon: dict[str] = DEFAULT, usetheme: b # Apply sv_ttk theme to the window if usetheme: - try: + try: from darkdetect import isDark from sv_ttk import set_theme - except: + except ImportError: usetheme = False else: set_theme("dark" if isDark() else "light") self.option_add("*font", ("Cascadia Mono", 9)) + # Enable window's darkmode if isDark(): from ctypes import byref, c_int, sizeof, windll @@ -127,7 +128,7 @@ def __init__(self, edit: bool = False, basedon: dict[str] = DEFAULT, usetheme: b background = Label(backgroundframe, text="Choose or input your normalbackground hex color") backgroundentry = Entry(backgroundframe) backgroundbutton = Button(backgroundframe, command=lambda: self.selectcolor(backgroundentry, "background")) - + # TODO: improve all the labels' text insertbackgroundframe = Frame(self) insertbackground = Label(insertbackgroundframe, text="Choose or input your insertbackground hex color") @@ -170,7 +171,7 @@ def __init__(self, edit: bool = False, basedon: dict[str] = DEFAULT, usetheme: b selectbackground=self.style["selectbackground"], selectforeground=self.style["selectforeground"], ) - + # Write down some example text self.render.insert("insert", "This is a normal text for test style.") self.render.tag_add("select", "1.28", "1.39") @@ -178,7 +179,7 @@ def __init__(self, edit: bool = False, basedon: dict[str] = DEFAULT, usetheme: b "select", background=self.style["selectbackground"], foreground=self.style["selectforeground"] ) self.render["state"] = "normal" if edit else "disable" - + # Apply the theme to the button widgets if usetheme is True if usetheme: for widget in ( @@ -197,14 +198,14 @@ def __init__(self, edit: bool = False, basedon: dict[str] = DEFAULT, usetheme: b self.style.values(), ): widget.insert("insert", hexcolor) - + # Bind some events backgroundentry.bind("", lambda event: self.checkhexcolor(event, "background")) insertbackgroundentry.bind("", lambda event: self.checkhexcolor(event, "insertbackground")) selectbackgroundentry.bind("", lambda event: self.checkhexcolor(event, "selectbackground")) selectforegroundentry.bind("", lambda event: self.checkhexcolor(event, "selectforeground")) foregroundentry.bind("", lambda event: self.checkhexcolor(event, "foreground")) - + # Pack the widgets cancel.pack(side="right", padx=1) save.pack(side="right", padx=3) @@ -284,10 +285,11 @@ def checkhexcolor(self, event: Event, name: str) -> None: else: event.widget.state(["!invalid"]) + # Load the custom style CUSTOM: dict[str] = load_style() if load_style() != {} else DEFAULT if __name__ == "__main__": # An example basedon "powershell" style and also use sv_ttk theme - configstyle = Config(edit = True, basedon=POWERSHELL, usetheme = True) + configstyle = Config(edit=True, basedon=POWERSHELL, usetheme=True) configstyle.mainloop() From 788dfd07c16a7eeabe8329fcdb4f66a63f42d2d5 Mon Sep 17 00:00:00 2001 From: XiaoBaiYun <71159641+littlewhitecloud@users.noreply.github.com> Date: Mon, 17 Jul 2023 16:05:24 +0800 Subject: [PATCH 32/34] fix #51 and also fix #52 --- tktermwidget/widgets.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tktermwidget/widgets.py b/tktermwidget/widgets.py index dd6bbe4..ac7a0f4 100644 --- a/tktermwidget/widgets.py +++ b/tktermwidget/widgets.py @@ -203,10 +203,13 @@ def execute(self, _: Event) -> str: if cmd in ["clear", "cls"]: self.text.delete("1.0", "end") self.directory() + self.index = 1 return "break" elif cmd == "exit": self.master.quit() elif cmd.startswith("cd"): # TAG: is all platform use cd...? + # It will raise OSError instead of output a normal error + # TODO: fix it if cmd == "cd..": chdir(path.abspath(path.join(getcwd(), ".."))) else: @@ -252,8 +255,6 @@ def execute(self, _: Event) -> str: # Update the text and the index self.index = int(self.text.index("insert").split(".")[0]) self.update() - - del cmd, errors, returncode, returnlines return "break" # Prevent the default newline character insertion def newline(self) -> None: @@ -263,6 +264,8 @@ def newline(self) -> None: def update(self) -> str: """Update the text widget or the command has no output""" + # Make a newline + self.newline() # Insert the directory self.directory() # Update cursor and check if it is out of the edit range From a546fa0a6012568b8ce02170a6064b3390a58795 Mon Sep 17 00:00:00 2001 From: XiaoBaiYun <71159641+littlewhitecloud@users.noreply.github.com> Date: Mon, 17 Jul 2023 18:50:28 +0800 Subject: [PATCH 33/34] I forget what I did I changed --- tktermwidget/utils.py | 32 +++++++++++++++----------------- tktermwidget/widgets.py | 6 +++--- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/tktermwidget/utils.py b/tktermwidget/utils.py index 82b6296..067e430 100644 --- a/tktermwidget/utils.py +++ b/tktermwidget/utils.py @@ -1,28 +1,23 @@ """Some useful tools""" +from pathlib import Path + +from platformdirs import user_cache_dir + +# Get the package path +PACKAGE_PATH = Path(user_cache_dir("tktermwidget")) +# Get the history file +HISTORY_FILE = PACKAGE_PATH / "history.txt" +# Get the json file (style) +JSON_FILE = PACKAGE_PATH / "styles.json" def check(): """Check files and create them if they don't exsit""" from json import dump - from pathlib import Path - - from platformdirs import user_cache_dir - # Get the package path - PACKAGE_PATH = Path(user_cache_dir("tktermwidget")) - # Get the history file - HISTORY_FILE = PACKAGE_PATH / "history.txt" - # Get the json file (style) - JSON_FILE = PACKAGE_PATH / "styles.json" - - if not PACKAGE_PATH.exists(): # Check the "tktermwidget" is exsit + # Check the "tktermwidget" is exsit + if not PACKAGE_PATH.exists(): PACKAGE_PATH.mkdir(parents=True) - # Also create the history file - with open(HISTORY_FILE, "w", encoding="utf-8") as f: - f.close() - # Also create the json file - with open(JSON_FILE, "w", encoding="utf-8") as f: - dump("{}", f) # Check that the history file exists if not (HISTORY_FILE).exists(): @@ -33,3 +28,6 @@ def check(): if not (JSON_FILE).exists(): with open(JSON_FILE, "w", encoding="utf-8") as f: dump("{}", f) + + +check() diff --git a/tktermwidget/widgets.py b/tktermwidget/widgets.py index ac7a0f4..34b36f8 100644 --- a/tktermwidget/widgets.py +++ b/tktermwidget/widgets.py @@ -30,7 +30,7 @@ class AutoHideScrollbar(Scrollbar): """Scrollbar that automatically hides when not needed""" - def __init__(self, master=None, **kwargs): + def __init__(self, master: Misc = None, **kwargs): Scrollbar.__init__(self, master=master, **kwargs) def set(self, first: int, last: int): @@ -208,8 +208,8 @@ def execute(self, _: Event) -> str: elif cmd == "exit": self.master.quit() elif cmd.startswith("cd"): # TAG: is all platform use cd...? - # It will raise OSError instead of output a normal error - # TODO: fix it + # It will raise OSError instead of output a normal error + # TODO: fix it if cmd == "cd..": chdir(path.abspath(path.join(getcwd(), ".."))) else: From ec0f3e7a506567270585edec84416e9134daf1ca Mon Sep 17 00:00:00 2001 From: XiaoBaiYun <71159641+littlewhitecloud@users.noreply.github.com> Date: Tue, 18 Jul 2023 09:20:38 +0800 Subject: [PATCH 34/34] fix SyntaxError: encoding problem: gbk --- README_CH.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README_CH.md b/README_CH.md index b6b638f..6c6eaa9 100644 --- a/README_CH.md +++ b/README_CH.md @@ -59,7 +59,7 @@ pip install tktermwidget ## 样例: ```python -# -*- coding: gbk -*- +"""An example for tktermwidget""" from tkinter import Tk from tkterm import Terminal