pymud 0.20.4__py3-none-any.whl → 0.21.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pymud/__init__.py +7 -2
- pymud/decorators.py +234 -0
- pymud/dialogs.py +37 -31
- pymud/extras.py +7 -135
- pymud/i18n.py +63 -0
- pymud/lang/i18n_chs.py +227 -0
- pymud/lang/i18n_eng.py +851 -0
- pymud/logger.py +9 -4
- pymud/main.py +123 -44
- pymud/modules.py +104 -31
- pymud/objects.py +102 -98
- pymud/pkuxkx.py +267 -142
- pymud/protocol.py +15 -13
- pymud/pymud.py +80 -75
- pymud/session.py +582 -356
- pymud/settings.py +15 -2
- {pymud-0.20.4.dist-info → pymud-0.21.0.dist-info}/METADATA +118 -5
- pymud-0.21.0.dist-info/RECORD +23 -0
- {pymud-0.20.4.dist-info → pymud-0.21.0.dist-info}/WHEEL +1 -1
- pymud-0.20.4.dist-info/RECORD +0 -19
- {pymud-0.20.4.dist-info → pymud-0.21.0.dist-info}/entry_points.txt +0 -0
- {pymud-0.20.4.dist-info → pymud-0.21.0.dist-info}/licenses/LICENSE.txt +0 -0
- {pymud-0.20.4.dist-info → pymud-0.21.0.dist-info}/top_level.txt +0 -0
pymud/__init__.py
CHANGED
@@ -1,12 +1,17 @@
|
|
1
1
|
from .settings import Settings
|
2
2
|
from .pymud import PyMudApp
|
3
|
-
from .modules import IConfig
|
3
|
+
from .modules import IConfigBase, IConfig, PymudMeta
|
4
4
|
from .objects import CodeBlock, Alias, SimpleAlias, Trigger, SimpleTrigger, Command, SimpleCommand, Timer, SimpleTimer, GMCPTrigger
|
5
5
|
from .extras import DotDict
|
6
6
|
from .session import Session
|
7
7
|
from .logger import Logger
|
8
8
|
from .main import main
|
9
|
+
from .decorators import exception, async_exception, PymudDecorator, alias, trigger, timer, gmcp
|
9
10
|
|
10
11
|
__all__ = [
|
11
|
-
"
|
12
|
+
"PymudMeta", "IConfigBase", "IConfig", "PyMudApp", "Settings", "CodeBlock",
|
13
|
+
"Alias", "SimpleAlias", "Trigger", "SimpleTrigger",
|
14
|
+
"Command", "SimpleCommand", "Timer", "SimpleTimer",
|
15
|
+
"GMCPTrigger", "Session", "DotDict", "Logger", "main",
|
16
|
+
"exception", "async_exception", "PymudDecorator", "alias", "trigger", "timer", "gmcp",
|
12
17
|
]
|
pymud/decorators.py
ADDED
@@ -0,0 +1,234 @@
|
|
1
|
+
import sys, functools, traceback
|
2
|
+
from typing import Union, Optional, List
|
3
|
+
|
4
|
+
def print_exception(session, e: Exception):
|
5
|
+
"""打印异常信息"""
|
6
|
+
from .settings import Settings
|
7
|
+
from .session import Session
|
8
|
+
if isinstance(session, Session):
|
9
|
+
# tb = sys.exc_info()[2]
|
10
|
+
# frames = traceback.extract_tb(tb)
|
11
|
+
# frame = frames[-1]
|
12
|
+
# session.error(Settings.gettext("exception_traceback", frame.filename, frame.lineno, frame.name), Settings.gettext("script_error"))
|
13
|
+
# if frame.line:
|
14
|
+
# session.error(f" {frame.line}", Settings.gettext("script_error"))
|
15
|
+
|
16
|
+
# session.error(Settings.gettext("exception_message", type(e).__name__, e), Settings.gettext("script_error"))
|
17
|
+
# session.error("===========================", Settings.gettext("script_error"))
|
18
|
+
session.error(traceback.format_exc(), Settings.gettext("script_error"))
|
19
|
+
|
20
|
+
def exception(func):
|
21
|
+
"""方法异常处理装饰器,捕获异常后通过会话的session.error打印相关信息"""
|
22
|
+
@functools.wraps(func)
|
23
|
+
def wrapper(self, *args, **kwargs):
|
24
|
+
from .objects import BaseObject
|
25
|
+
from .modules import ModuleInfo, IConfig
|
26
|
+
from .session import Session
|
27
|
+
from .settings import Settings
|
28
|
+
try:
|
29
|
+
return func(self, *args, **kwargs)
|
30
|
+
except Exception as e:
|
31
|
+
# 调用类的错误处理方法
|
32
|
+
if isinstance(self, Session):
|
33
|
+
session = self
|
34
|
+
elif isinstance(self, (BaseObject, IConfig, ModuleInfo)):
|
35
|
+
session = self.session
|
36
|
+
else:
|
37
|
+
session = None
|
38
|
+
|
39
|
+
if isinstance(session, Session):
|
40
|
+
print_exception(session, e)
|
41
|
+
#session.error(Settings.gettext("exception_message", e, type(e)))
|
42
|
+
#session.error(Settings.gettext("exception_traceback", traceback.format_exc()))
|
43
|
+
else:
|
44
|
+
raise # 当没有会话时,选择重新抛出异常
|
45
|
+
return wrapper
|
46
|
+
|
47
|
+
def async_exception(func):
|
48
|
+
"""异步方法异常处理装饰器,捕获异常后通过会话的session.error打印相关信息"""
|
49
|
+
@functools.wraps(func)
|
50
|
+
async def wrapper(self, *args, **kwargs):
|
51
|
+
from .objects import BaseObject
|
52
|
+
from .modules import ModuleInfo, IConfig
|
53
|
+
from .session import Session
|
54
|
+
from .settings import Settings
|
55
|
+
try:
|
56
|
+
return await func(self, *args, **kwargs)
|
57
|
+
except Exception as e:
|
58
|
+
if isinstance(self, Session):
|
59
|
+
session = self
|
60
|
+
elif isinstance(self, (BaseObject, IConfig, ModuleInfo)):
|
61
|
+
session = self.session
|
62
|
+
else:
|
63
|
+
session = None
|
64
|
+
|
65
|
+
if isinstance(session, Session):
|
66
|
+
print_exception(session, e)
|
67
|
+
|
68
|
+
else:
|
69
|
+
raise # 当没有会话时,选择重新抛出异常
|
70
|
+
return wrapper
|
71
|
+
|
72
|
+
|
73
|
+
class PymudDecorator:
|
74
|
+
"""
|
75
|
+
装饰器基类。使用装饰器可以快捷创建各类Pymud基础对象。
|
76
|
+
|
77
|
+
:param type: 装饰器的类型,用于区分不同的装饰器,为字符串类型。
|
78
|
+
:param args: 可变位置参数,用于传递额外的参数。
|
79
|
+
:param kwargs: 可变关键字参数,用于传递额外的键值对参数。
|
80
|
+
"""
|
81
|
+
def __init__(self, type: str, *args, **kwargs):
|
82
|
+
self.type = type
|
83
|
+
self.args = args
|
84
|
+
self.kwargs = kwargs
|
85
|
+
|
86
|
+
def __call__(self, func):
|
87
|
+
decos = getattr(func, "__pymud_decorators__", [])
|
88
|
+
decos.append(self)
|
89
|
+
func.__pymud_decorators__ = decos
|
90
|
+
|
91
|
+
return func
|
92
|
+
|
93
|
+
def __repr__(self):
|
94
|
+
return f"<{self.__class__.__name__} type = {self.type} args = {self.args} kwargs = {self.kwargs}>"
|
95
|
+
|
96
|
+
|
97
|
+
def alias(
|
98
|
+
patterns: Union[List[str], str],
|
99
|
+
id: Optional[str] = None,
|
100
|
+
group: str = "",
|
101
|
+
enabled: bool = True,
|
102
|
+
ignoreCase: bool = False,
|
103
|
+
isRegExp: bool = True,
|
104
|
+
keepEval: bool = False,
|
105
|
+
expandVar: bool = True,
|
106
|
+
priority: int = 100,
|
107
|
+
oneShot: bool = False,
|
108
|
+
*args, **kwargs):
|
109
|
+
"""
|
110
|
+
使用装饰器创建一个别名(Alias)对象。被装饰的函数将在别名成功匹配时调用。
|
111
|
+
被装饰的函数,除第一个参数为类实例本身self之外,另外包括id, line, wildcards三个参数。
|
112
|
+
其中id为别名对象的唯一标识符,line为匹配的文本行,wildcards为匹配的通配符列表。
|
113
|
+
|
114
|
+
:param patterns: 别名匹配的模式。
|
115
|
+
:param id: 别名对象的唯一标识符,不指定时将自动生成唯一标识符。
|
116
|
+
:param group: 别名所属的组名,默认为空字符串。
|
117
|
+
:param enabled: 别名是否启用,默认为 True。
|
118
|
+
:param ignoreCase: 匹配时是否忽略大小写,默认为 False。
|
119
|
+
:param isRegExp: 模式是否为正则表达式,默认为 True。
|
120
|
+
:param keepEval: 若存在多个可匹配的别名时,是否持续匹配,默认为 False。
|
121
|
+
:param expandVar: 是否展开变量,默认为 True。
|
122
|
+
:param priority: 别名的优先级,值越小优先级越高,默认为 100。
|
123
|
+
:param oneShot: 别名是否只触发一次后自动失效,默认为 False。
|
124
|
+
:param args: 可变位置参数,用于传递额外的参数。
|
125
|
+
:param kwargs: 可变关键字参数,用于传递额外的键值对参数。
|
126
|
+
:return: PymudDecorator 实例,类型为 "alias"。
|
127
|
+
"""
|
128
|
+
# 将传入的参数更新到 kwargs 字典中
|
129
|
+
kwargs.update({
|
130
|
+
"patterns": patterns,
|
131
|
+
"id": id,
|
132
|
+
"group": group,
|
133
|
+
"enabled": enabled,
|
134
|
+
"ignoreCase": ignoreCase,
|
135
|
+
"isRegExp": isRegExp,
|
136
|
+
"keepEval": keepEval,
|
137
|
+
"expandVar": expandVar,
|
138
|
+
"priority": priority,
|
139
|
+
"oneShot": oneShot})
|
140
|
+
|
141
|
+
# 如果 id 为 None,则从 kwargs 中移除 "id" 键
|
142
|
+
if not id:
|
143
|
+
kwargs.pop("id")
|
144
|
+
|
145
|
+
return PymudDecorator("alias", *args, **kwargs)
|
146
|
+
|
147
|
+
def trigger(
|
148
|
+
patterns: Union[List[str], str],
|
149
|
+
id: Optional[str] = None,
|
150
|
+
group: str = "",
|
151
|
+
enabled: bool = True,
|
152
|
+
ignoreCase: bool = False,
|
153
|
+
isRegExp: bool = True,
|
154
|
+
keepEval: bool = False,
|
155
|
+
expandVar: bool = True,
|
156
|
+
raw: bool = False,
|
157
|
+
priority: int = 100,
|
158
|
+
oneShot: bool = False,
|
159
|
+
*args, **kwargs):
|
160
|
+
"""
|
161
|
+
使用装饰器创建一个触发器(Trigger)对象。
|
162
|
+
|
163
|
+
:param patterns: 触发器匹配的模式。单行模式可以是字符串或正则表达式,多行模式必须是元组或列表,其中每个元素都是字符串或正则表达式。
|
164
|
+
:param id: 触发器对象的唯一标识符,不指定时将自动生成唯一标识符。
|
165
|
+
:param group: 触发器所属的组名,默认为空字符串。
|
166
|
+
:param enabled: 触发器是否启用,默认为 True。
|
167
|
+
:param ignoreCase: 匹配时是否忽略大小写,默认为 False。
|
168
|
+
:param isRegExp: 模式是否为正则表达式,默认为 True。
|
169
|
+
:param keepEval: 若存在多个可匹配的触发器时,是否持续匹配,默认为 False。
|
170
|
+
:param expandVar: 是否展开变量,默认为 True。
|
171
|
+
:param raw: 是否使用原始匹配方式,默认为 False。原始匹配方式下,不对 VT100 下的 ANSI 颜色进行解码,因此可以匹配颜色;正常匹配仅匹配文本。
|
172
|
+
:param priority: 触发器的优先级,值越小优先级越高,默认为 100。
|
173
|
+
:param oneShot: 触发器是否只触发一次后自动失效,默认为 False。
|
174
|
+
:param args: 可变位置参数,用于传递额外的参数。
|
175
|
+
:param kwargs: 可变关键字参数,用于传递额外的键值对参数。
|
176
|
+
:return: PymudDecorator 实例,类型为 "trigger"。
|
177
|
+
"""
|
178
|
+
# 将传入的参数更新到 kwargs 字典中
|
179
|
+
kwargs.update({
|
180
|
+
"patterns": patterns,
|
181
|
+
"id": id,
|
182
|
+
"group": group,
|
183
|
+
"enabled": enabled,
|
184
|
+
"ignoreCase": ignoreCase,
|
185
|
+
"isRegExp": isRegExp,
|
186
|
+
"keepEval": keepEval,
|
187
|
+
"expandVar": expandVar,
|
188
|
+
"raw": raw,
|
189
|
+
"priority": priority,
|
190
|
+
"oneShot": oneShot})
|
191
|
+
if not id:
|
192
|
+
kwargs.pop("id")
|
193
|
+
return PymudDecorator("trigger", *args, **kwargs)
|
194
|
+
|
195
|
+
def timer(timeout: float, id: Optional[str] = None, group: str = "", enabled: bool = True, *args, **kwargs):
|
196
|
+
"""
|
197
|
+
使用装饰器创建一个定时器(Timer)对象。
|
198
|
+
|
199
|
+
:param timeout: 定时器超时时间,单位为秒。
|
200
|
+
:param id: 定时器对象的唯一标识符,不指定时将自动生成唯一标识符。
|
201
|
+
:param group: 定时器所属的组名,默认为空字符串。
|
202
|
+
:param enabled: 定时器是否启用,默认为 True。
|
203
|
+
:param args: 可变位置参数,用于传递额外的参数。
|
204
|
+
:param kwargs: 可变关键字参数,用于传递额外的键值对参数。
|
205
|
+
:return: PymudDecorator 实例,类型为 "timer"。
|
206
|
+
"""
|
207
|
+
kwargs.update({
|
208
|
+
"timeout": timeout,
|
209
|
+
"id": id,
|
210
|
+
"group": group,
|
211
|
+
"enabled": enabled
|
212
|
+
})
|
213
|
+
if not id:
|
214
|
+
kwargs.pop("id")
|
215
|
+
return PymudDecorator("timer", *args, **kwargs)
|
216
|
+
|
217
|
+
def gmcp(name: str, group: str = "", enabled: bool = True, *args, **kwargs):
|
218
|
+
"""
|
219
|
+
使用装饰器创建一个GMCP触发器(GMCPTrigger)对象。
|
220
|
+
|
221
|
+
:param name: GMCP触发器的名称。
|
222
|
+
:param group: GMCP触发器所属的组名,默认为空字符串。
|
223
|
+
:param enabled: GMCP触发器是否启用,默认为 True。
|
224
|
+
:param args: 可变位置参数,用于传递额外的参数。
|
225
|
+
:param kwargs: 可变关键字参数,用于传递额外的键值对参数。
|
226
|
+
:return: PymudDecorator 实例,类型为 "gmcp"。
|
227
|
+
"""
|
228
|
+
kwargs.update({
|
229
|
+
"id": name,
|
230
|
+
"group": group,
|
231
|
+
"enabled": enabled
|
232
|
+
})
|
233
|
+
|
234
|
+
return PymudDecorator("gmcp", *args, **kwargs)
|
pymud/dialogs.py
CHANGED
@@ -1,18 +1,18 @@
|
|
1
1
|
import asyncio, webbrowser
|
2
|
-
|
2
|
+
from typing import Any, Callable, Iterable, List, Tuple, Union
|
3
3
|
from prompt_toolkit.layout import AnyContainer, ConditionalContainer, Float, VSplit, HSplit, Window, WindowAlign, ScrollablePane, ScrollOffsets
|
4
4
|
from prompt_toolkit.widgets import Button, Dialog, Label, MenuContainer, MenuItem, TextArea, SystemToolbar, Frame, RadioList
|
5
5
|
from prompt_toolkit.layout.dimension import Dimension, D
|
6
6
|
from prompt_toolkit import ANSI, HTML
|
7
7
|
from prompt_toolkit.mouse_events import MouseEvent, MouseEventType
|
8
|
-
from prompt_toolkit.formatted_text import FormattedText
|
8
|
+
from prompt_toolkit.formatted_text import FormattedText, AnyFormattedText
|
9
9
|
from prompt_toolkit.application.current import get_app
|
10
10
|
from .extras import EasternButton
|
11
11
|
|
12
12
|
from .settings import Settings
|
13
13
|
|
14
14
|
class BasicDialog:
|
15
|
-
def __init__(self, title = "", modal = True):
|
15
|
+
def __init__(self, title: AnyFormattedText = "", modal = True):
|
16
16
|
self.future = asyncio.Future()
|
17
17
|
self.dialog = Dialog(
|
18
18
|
body = self.create_body(),
|
@@ -22,14 +22,14 @@ class BasicDialog:
|
|
22
22
|
width = D(preferred=80),
|
23
23
|
)
|
24
24
|
|
25
|
-
def set_done(self, result = True):
|
25
|
+
def set_done(self, result: Any = True):
|
26
26
|
self.future.set_result(result)
|
27
27
|
|
28
28
|
def create_body(self) -> AnyContainer:
|
29
|
-
return HSplit([Label(text="
|
29
|
+
return HSplit([Label(text=Settings.gettext("basic_dialog"))])
|
30
30
|
|
31
31
|
def create_buttons(self):
|
32
|
-
ok_button = EasternButton(text="
|
32
|
+
ok_button = EasternButton(text=Settings.gettext("ok"), handler=(lambda: self.set_done()))
|
33
33
|
return [ok_button]
|
34
34
|
|
35
35
|
def set_exception(self, exc):
|
@@ -47,7 +47,7 @@ class MessageDialog(BasicDialog):
|
|
47
47
|
return HSplit([Label(text=self.message)])
|
48
48
|
|
49
49
|
class QueryDialog(BasicDialog):
|
50
|
-
def __init__(self, title="", message = "", modal=True):
|
50
|
+
def __init__(self, title: AnyFormattedText = "", message: AnyFormattedText = "", modal = True):
|
51
51
|
self.message = message
|
52
52
|
super().__init__(title, modal)
|
53
53
|
|
@@ -55,17 +55,17 @@ class QueryDialog(BasicDialog):
|
|
55
55
|
return HSplit([Label(text=self.message)])
|
56
56
|
|
57
57
|
def create_buttons(self):
|
58
|
-
ok_button = EasternButton(text="
|
59
|
-
cancel_button = EasternButton(text="
|
58
|
+
ok_button = EasternButton(text=Settings.gettext("ok"), handler=(lambda: self.set_done(True)))
|
59
|
+
cancel_button = EasternButton(text=Settings.gettext("cancel"), handler=(lambda: self.set_done(False)))
|
60
60
|
return [ok_button, cancel_button]
|
61
61
|
|
62
62
|
class WelcomeDialog(BasicDialog):
|
63
63
|
def __init__(self, modal=True):
|
64
64
|
self.website = FormattedText(
|
65
|
-
[('', '
|
65
|
+
[('', f'{Settings.gettext("visit")} '),
|
66
66
|
#('class:b', 'GitHub:'),
|
67
|
-
('',
|
68
|
-
('', '
|
67
|
+
('', Settings.__website__, self.open_url),
|
68
|
+
('', f' {Settings.gettext("displayhelp")}')]
|
69
69
|
)
|
70
70
|
super().__init__("PYMUD", modal)
|
71
71
|
|
@@ -77,10 +77,10 @@ class WelcomeDialog(BasicDialog):
|
|
77
77
|
import platform, sys
|
78
78
|
body = HSplit([
|
79
79
|
Window(height=1),
|
80
|
-
Label(HTML(
|
81
|
-
Label(HTML(
|
80
|
+
Label(HTML(Settings.gettext("appinfo", Settings.__version__, Settings.__release__)), align=WindowAlign.CENTER),
|
81
|
+
Label(HTML(Settings.gettext("author", Settings.__author__, Settings.__email__)), align=WindowAlign.CENTER),
|
82
82
|
Label(self.website, align=WindowAlign.CENTER),
|
83
|
-
Label(
|
83
|
+
Label(Settings.gettext("sysversion", platform.system(), platform.version(), platform.python_version()), align = WindowAlign.CENTER),
|
84
84
|
|
85
85
|
Window(height=1),
|
86
86
|
])
|
@@ -89,25 +89,25 @@ class WelcomeDialog(BasicDialog):
|
|
89
89
|
|
90
90
|
class NewSessionDialog(BasicDialog):
|
91
91
|
def __init__(self):
|
92
|
-
super().__init__("
|
92
|
+
super().__init__(Settings.gettext("new_session"), True)
|
93
93
|
|
94
94
|
def create_body(self) -> AnyContainer:
|
95
95
|
body = HSplit([
|
96
96
|
VSplit([
|
97
97
|
HSplit([
|
98
|
-
Label("
|
98
|
+
Label(f" {Settings.gettext('sessionname')}:"),
|
99
99
|
Frame(body=TextArea(name = "session", text="session", multiline=False, wrap_lines=False, height = 1, dont_extend_height=True, width = D(preferred=10), focus_on_click=True, read_only=False),)
|
100
100
|
]),
|
101
101
|
HSplit([
|
102
|
-
Label("
|
102
|
+
Label(f" {Settings.gettext('host')}:"),
|
103
103
|
Frame(body=TextArea(name = "host", text="mud.pkuxkx.net", multiline=False, wrap_lines=False, height = 1, dont_extend_height=True, width = D(preferred=20), focus_on_click=True, read_only=False),)
|
104
104
|
]),
|
105
105
|
HSplit([
|
106
|
-
Label("
|
106
|
+
Label(f" {Settings.gettext('port')}:"),
|
107
107
|
Frame(body=TextArea(name = "port", text="8081", multiline=False, wrap_lines=False, height = 1, dont_extend_height=True, width = D(max=8), focus_on_click=True, read_only=False),)
|
108
108
|
]),
|
109
109
|
HSplit([
|
110
|
-
Label("
|
110
|
+
Label(f" {Settings.gettext('encoding')}:"),
|
111
111
|
Frame(body=TextArea(name = "encoding", text="utf8", multiline=False, wrap_lines=False, height = 1, dont_extend_height=True, width = D(max=8), focus_on_click=True, read_only=False),)
|
112
112
|
]),
|
113
113
|
])
|
@@ -116,15 +116,18 @@ class NewSessionDialog(BasicDialog):
|
|
116
116
|
return body
|
117
117
|
|
118
118
|
def create_buttons(self):
|
119
|
-
ok_button = EasternButton(text="
|
120
|
-
cancel_button = EasternButton(text="
|
119
|
+
ok_button = EasternButton(text=Settings.gettext("ok"), handler=self.btn_ok_clicked)
|
120
|
+
cancel_button = EasternButton(text=Settings.gettext("cancel"), handler=(lambda: self.set_done(False)))
|
121
121
|
return [ok_button, cancel_button]
|
122
122
|
|
123
123
|
def btn_ok_clicked(self):
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
124
|
+
def get_text_safely(buffer_name):
|
125
|
+
buffer = get_app().layout.get_buffer_by_name(buffer_name)
|
126
|
+
return buffer.text if buffer else ""
|
127
|
+
name = get_text_safely("session")
|
128
|
+
host = get_text_safely("host")
|
129
|
+
port = get_text_safely("port")
|
130
|
+
encoding = get_text_safely("encoding")
|
128
131
|
result = (name, host, port, encoding)
|
129
132
|
self.set_done(result)
|
130
133
|
|
@@ -137,8 +140,8 @@ class LogSelectionDialog(BasicDialog):
|
|
137
140
|
if len(values) > 0:
|
138
141
|
self._radio_list = RadioList(values = self._selection_values)
|
139
142
|
else:
|
140
|
-
self._radio_list = Label(
|
141
|
-
super().__init__(
|
143
|
+
self._radio_list = Label(Settings.gettext("nolog").center(13))
|
144
|
+
super().__init__(Settings.gettext("chooselog"), modal)
|
142
145
|
|
143
146
|
def create_body(self) -> AnyContainer:
|
144
147
|
body=HSplit([
|
@@ -148,13 +151,16 @@ class LogSelectionDialog(BasicDialog):
|
|
148
151
|
return body
|
149
152
|
|
150
153
|
def create_buttons(self):
|
151
|
-
ok_button = EasternButton(text="
|
152
|
-
cancel_button = EasternButton(text="
|
154
|
+
ok_button = EasternButton(text=Settings.gettext("ok"), handler=self.btn_ok_clicked)
|
155
|
+
cancel_button = EasternButton(text=Settings.gettext("cancel"), handler=(lambda: self.set_done(False)))
|
153
156
|
return [ok_button, cancel_button]
|
154
157
|
|
155
158
|
def btn_ok_clicked(self):
|
156
159
|
if self._itemsCount:
|
157
|
-
|
160
|
+
if isinstance(self._radio_list, RadioList):
|
161
|
+
result = self._radio_list.current_value
|
162
|
+
else:
|
163
|
+
result = None
|
158
164
|
self.set_done(result)
|
159
165
|
else:
|
160
166
|
self.set_done(False)
|
pymud/extras.py
CHANGED
@@ -3,7 +3,7 @@ from unicodedata import east_asian_width
|
|
3
3
|
from wcwidth import wcwidth
|
4
4
|
import time, re, logging
|
5
5
|
|
6
|
-
from typing import Iterable
|
6
|
+
from typing import Iterable, Optional, Union, Tuple
|
7
7
|
from prompt_toolkit import ANSI
|
8
8
|
from prompt_toolkit.application import get_app
|
9
9
|
from prompt_toolkit.buffer import Buffer
|
@@ -225,11 +225,11 @@ class SessionBuffer(Buffer):
|
|
225
225
|
|
226
226
|
# End of <getters/setters>
|
227
227
|
|
228
|
-
def save_to_undo_stack(self, clear_redo_stack: bool = True) -> None:
|
229
|
-
|
228
|
+
# def save_to_undo_stack(self, clear_redo_stack: bool = True) -> None:
|
229
|
+
# pass
|
230
230
|
|
231
|
-
def delete(self, count: int = 1) -> str:
|
232
|
-
|
231
|
+
# def delete(self, count: int = 1) -> str:
|
232
|
+
# pass
|
233
233
|
|
234
234
|
def insert_text(
|
235
235
|
self,
|
@@ -291,116 +291,12 @@ class SessionBuffer(Buffer):
|
|
291
291
|
|
292
292
|
|
293
293
|
class SessionBufferControl(BufferControl):
|
294
|
-
def __init__(self, buffer: SessionBuffer = None, input_processors = None, include_default_input_processors: bool = True, lexer: Lexer = None, preview_search: FilterOrBool = False, focusable: FilterOrBool = True, search_buffer_control = None, menu_position = None, focus_on_click: FilterOrBool = False, key_bindings: KeyBindingsBase = None):
|
294
|
+
def __init__(self, buffer: Optional[SessionBuffer] = None, input_processors = None, include_default_input_processors: bool = True, lexer: Optional[Lexer] = None, preview_search: FilterOrBool = False, focusable: FilterOrBool = True, search_buffer_control = None, menu_position = None, focus_on_click: FilterOrBool = False, key_bindings: Optional[KeyBindingsBase] = None):
|
295
295
|
# 将所属Buffer类型更改为SessionBuffer
|
296
296
|
buffer = buffer or SessionBuffer()
|
297
297
|
super().__init__(buffer, input_processors, include_default_input_processors, lexer, preview_search, focusable, search_buffer_control, menu_position, focus_on_click, key_bindings)
|
298
298
|
self.buffer = buffer
|
299
299
|
|
300
|
-
# def create_content(
|
301
|
-
# self, width: int, height: int, preview_search: bool = False
|
302
|
-
# ) -> UIContent:
|
303
|
-
# """
|
304
|
-
# Create a UIContent.
|
305
|
-
# """
|
306
|
-
# buffer = self.buffer
|
307
|
-
|
308
|
-
# # Trigger history loading of the buffer. We do this during the
|
309
|
-
# # rendering of the UI here, because it needs to happen when an
|
310
|
-
# # `Application` with its event loop is running. During the rendering of
|
311
|
-
# # the buffer control is the earliest place we can achieve this, where
|
312
|
-
# # we're sure the right event loop is active, and don't require user
|
313
|
-
# # interaction (like in a key binding).
|
314
|
-
# buffer.load_history_if_not_yet_loaded()
|
315
|
-
|
316
|
-
# # Get the document to be shown. If we are currently searching (the
|
317
|
-
# # search buffer has focus, and the preview_search filter is enabled),
|
318
|
-
# # then use the search document, which has possibly a different
|
319
|
-
# # text/cursor position.)
|
320
|
-
# search_control = self.search_buffer_control
|
321
|
-
# preview_now = preview_search or bool(
|
322
|
-
# # Only if this feature is enabled.
|
323
|
-
# self.preview_search()
|
324
|
-
# and
|
325
|
-
# # And something was typed in the associated search field.
|
326
|
-
# search_control
|
327
|
-
# and search_control.buffer.text
|
328
|
-
# and
|
329
|
-
# # And we are searching in this control. (Many controls can point to
|
330
|
-
# # the same search field, like in Pyvim.)
|
331
|
-
# get_app().layout.search_target_buffer_control == self
|
332
|
-
# )
|
333
|
-
|
334
|
-
# if preview_now and search_control is not None:
|
335
|
-
# ss = self.search_state
|
336
|
-
|
337
|
-
# document = buffer.document_for_search(
|
338
|
-
# SearchState(
|
339
|
-
# text=search_control.buffer.text,
|
340
|
-
# direction=ss.direction,
|
341
|
-
# ignore_case=ss.ignore_case,
|
342
|
-
# )
|
343
|
-
# )
|
344
|
-
# else:
|
345
|
-
# document = buffer.document
|
346
|
-
|
347
|
-
# get_processed_line = self._create_get_processed_line_func(
|
348
|
-
# document, width, height
|
349
|
-
# )
|
350
|
-
# self._last_get_processed_line = get_processed_line
|
351
|
-
|
352
|
-
# def translate_rowcol(row: int, col: int) -> Point:
|
353
|
-
# "Return the content column for this coordinate."
|
354
|
-
# return Point(x=get_processed_line(row).source_to_display(col), y=row)
|
355
|
-
|
356
|
-
# def get_line(i: int) -> StyleAndTextTuples:
|
357
|
-
# "Return the fragments for a given line number."
|
358
|
-
# fragments = get_processed_line(i).fragments
|
359
|
-
|
360
|
-
# # Add a space at the end, because that is a possible cursor
|
361
|
-
# # position. (When inserting after the input.) We should do this on
|
362
|
-
# # all the lines, not just the line containing the cursor. (Because
|
363
|
-
# # otherwise, line wrapping/scrolling could change when moving the
|
364
|
-
# # cursor around.)
|
365
|
-
# fragments = fragments + [("", " ")]
|
366
|
-
# return fragments
|
367
|
-
|
368
|
-
# content = UIContent(
|
369
|
-
# get_line=get_line,
|
370
|
-
# line_count=document.line_count,
|
371
|
-
# cursor_position=translate_rowcol(
|
372
|
-
# document.cursor_position_row, document.cursor_position_col
|
373
|
-
# ),
|
374
|
-
# )
|
375
|
-
|
376
|
-
# # If there is an auto completion going on, use that start point for a
|
377
|
-
# # pop-up menu position. (But only when this buffer has the focus --
|
378
|
-
# # there is only one place for a menu, determined by the focused buffer.)
|
379
|
-
# if get_app().layout.current_control == self:
|
380
|
-
# menu_position = self.menu_position() if self.menu_position else None
|
381
|
-
# if menu_position is not None:
|
382
|
-
# assert isinstance(menu_position, int)
|
383
|
-
# menu_row, menu_col = buffer.document.translate_index_to_position(
|
384
|
-
# menu_position
|
385
|
-
# )
|
386
|
-
# content.menu_position = translate_rowcol(menu_row, menu_col)
|
387
|
-
# elif buffer.complete_state:
|
388
|
-
# # Position for completion menu.
|
389
|
-
# # Note: We use 'min', because the original cursor position could be
|
390
|
-
# # behind the input string when the actual completion is for
|
391
|
-
# # some reason shorter than the text we had before. (A completion
|
392
|
-
# # can change and shorten the input.)
|
393
|
-
# menu_row, menu_col = buffer.document.translate_index_to_position(
|
394
|
-
# min(
|
395
|
-
# buffer.cursor_position,
|
396
|
-
# buffer.complete_state.original_document.cursor_position,
|
397
|
-
# )
|
398
|
-
# )
|
399
|
-
# content.menu_position = translate_rowcol(menu_row, menu_col)
|
400
|
-
# else:
|
401
|
-
# content.menu_position = None
|
402
|
-
|
403
|
-
# return content
|
404
300
|
|
405
301
|
def mouse_handler(self, mouse_event: MouseEvent):
|
406
302
|
"""
|
@@ -414,7 +310,7 @@ class SessionBufferControl(BufferControl):
|
|
414
310
|
# Focus buffer when clicked.
|
415
311
|
cur_control = get_app().layout.current_control
|
416
312
|
cur_buffer = get_app().layout.current_buffer
|
417
|
-
#
|
313
|
+
# 这里是修改的内容
|
418
314
|
if (cur_control == self) or (cur_buffer and cur_buffer.name == "input"):
|
419
315
|
if self._last_get_processed_line:
|
420
316
|
processed_line = self._last_get_processed_line(position.y)
|
@@ -982,30 +878,6 @@ class EasternMenuContainer(MenuContainer):
|
|
982
878
|
|
983
879
|
|
984
880
|
|
985
|
-
class MenuItem:
|
986
|
-
def __init__(
|
987
|
-
self,
|
988
|
-
text: str = "",
|
989
|
-
handler = None,
|
990
|
-
children = None,
|
991
|
-
shortcut = None,
|
992
|
-
disabled: bool = False,
|
993
|
-
) -> None:
|
994
|
-
self.text = text
|
995
|
-
self.handler = handler
|
996
|
-
self.children = children or []
|
997
|
-
self.shortcut = shortcut
|
998
|
-
self.disabled = disabled
|
999
|
-
self.selected_item = 0
|
1000
|
-
|
1001
|
-
@property
|
1002
|
-
def width(self) -> int:
|
1003
|
-
if self.children:
|
1004
|
-
return max(get_cwidth(c.text) for c in self.children)
|
1005
|
-
else:
|
1006
|
-
return 0
|
1007
|
-
|
1008
|
-
|
1009
881
|
class DotDict(dict):
|
1010
882
|
"""
|
1011
883
|
可以通过点.访问内部key-value对的字典。此类型继承自dict。
|
pymud/i18n.py
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
# internationalization (i18n)
|
2
|
+
import os, importlib
|
3
|
+
|
4
|
+
def i18n_ListAvailableLanguages():
|
5
|
+
"""
|
6
|
+
List all available languages.
|
7
|
+
|
8
|
+
This function checks all files in the `lang` directory for files starting with `i18n.` and ending with `.py`.
|
9
|
+
These files represent internationalization configurations for different languages. The default language is Simplified Chinese ("chs").
|
10
|
+
|
11
|
+
Returns:
|
12
|
+
list: A list containing all available language codes.
|
13
|
+
"""
|
14
|
+
# Define the default language list, here the default language is Simplified Chinese
|
15
|
+
languages = []
|
16
|
+
# Define the directory where the language files are located
|
17
|
+
lang_dir = os.path.join(os.path.dirname(__file__), "lang")
|
18
|
+
|
19
|
+
# Check if the language directory exists. If it doesn't, return the default language list directly
|
20
|
+
if os.path.exists(lang_dir):
|
21
|
+
# Iterate through all files in the language directory
|
22
|
+
for filename in os.listdir(lang_dir):
|
23
|
+
# Check if the file starts with "i18n.", ends with ".py", and is not the default Simplified Chinese file
|
24
|
+
if filename.startswith("i18n_") and filename.endswith(".py"):
|
25
|
+
# Extract the language code from the filename, removing "i18n." and ".py"
|
26
|
+
language = filename[5:-3]
|
27
|
+
# Add the extracted language code to the list of available languages
|
28
|
+
languages.append(language)
|
29
|
+
|
30
|
+
if not languages:
|
31
|
+
languages.append("chs")
|
32
|
+
|
33
|
+
return languages
|
34
|
+
|
35
|
+
def i18n_LoadLanguage(lang: str):
|
36
|
+
lang_file = os.path.join(os.path.dirname(__file__), "lang", f"i18n_{lang}.py")
|
37
|
+
if os.path.exists(lang_file):
|
38
|
+
modLang = importlib.import_module(f"pymud.lang.i18n_{lang}")
|
39
|
+
TRANS = modLang.__dict__["TRANSLATION"]
|
40
|
+
if isinstance(TRANS, dict):
|
41
|
+
from .settings import Settings
|
42
|
+
Settings.text.update(TRANS["text"])
|
43
|
+
|
44
|
+
if "docstring" in TRANS.keys():
|
45
|
+
docstring = TRANS["docstring"]
|
46
|
+
if isinstance(docstring, dict):
|
47
|
+
if "Session" in docstring.keys():
|
48
|
+
from .session import Session
|
49
|
+
docstring_class_session = docstring["Session"]
|
50
|
+
if isinstance(docstring_class_session, dict):
|
51
|
+
for key, newdoc in docstring_class_session.items():
|
52
|
+
if hasattr(Session, key):
|
53
|
+
obj = getattr(Session, key)
|
54
|
+
obj.__doc__ = newdoc
|
55
|
+
|
56
|
+
if "PyMudApp" in docstring.keys():
|
57
|
+
from .pymud import PyMudApp
|
58
|
+
docstring_class_pymudapp = docstring["PyMudApp"]
|
59
|
+
if isinstance(docstring_class_pymudapp, dict):
|
60
|
+
for key, newdoc in docstring_class_pymudapp.items():
|
61
|
+
if hasattr(PyMudApp, key):
|
62
|
+
obj = getattr(PyMudApp, key)
|
63
|
+
obj.__doc__ = newdoc
|