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