pymud 0.19.4__py3-none-any.whl → 0.20.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/pymud.py CHANGED
@@ -1,6 +1,7 @@
1
- import asyncio, functools, re, logging, math, json, os, webbrowser
2
- import importlib.util
3
- from prompt_toolkit.shortcuts import set_title
1
+ import asyncio, functools, re, os, webbrowser, threading
2
+ from datetime import datetime
3
+ from pathlib import Path
4
+ from prompt_toolkit.shortcuts import set_title, radiolist_dialog
4
5
  from prompt_toolkit.output import ColorDepth
5
6
  from prompt_toolkit.clipboard.pyperclip import PyperclipClipboard
6
7
  from prompt_toolkit import HTML
@@ -8,7 +9,7 @@ from prompt_toolkit.buffer import Buffer
8
9
  from prompt_toolkit.application import Application
9
10
  from prompt_toolkit.filters import Condition
10
11
  from prompt_toolkit.key_binding import KeyBindings
11
- from prompt_toolkit.layout import ConditionalContainer, Float, VSplit, HSplit, Window, WindowAlign
12
+ from prompt_toolkit.layout import ConditionalContainer, Float, VSplit, HSplit, Window, WindowAlign, ScrollbarMargin, NumberedMargin
12
13
  from prompt_toolkit.layout.layout import Layout
13
14
  from prompt_toolkit.layout.controls import FormattedTextControl
14
15
  from prompt_toolkit.layout.dimension import D
@@ -35,10 +36,11 @@ from prompt_toolkit.layout.processors import (
35
36
  from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
36
37
 
37
38
  from .objects import CodeBlock
38
- from .extras import MudFormatProcessor, SessionBuffer, EasternMenuContainer, VSplitWindow, SessionBufferControl, DotDict, Plugin
39
+ from .extras import MudFormatProcessor, SessionBuffer, EasternMenuContainer, VSplitWindow, SessionBufferControl, DotDict
40
+ from .modules import Plugin
39
41
  from .session import Session
40
42
  from .settings import Settings
41
- from .dialogs import MessageDialog, WelcomeDialog, QueryDialog, NewSessionDialog
43
+ from .dialogs import MessageDialog, WelcomeDialog, QueryDialog, NewSessionDialog, LogSelectionDialog
42
44
 
43
45
  from enum import Enum
44
46
 
@@ -140,6 +142,12 @@ class PyMudApp:
140
142
  set_title("{} {}".format(Settings.__appname__, Settings.__version__))
141
143
  self.set_status(Settings.text["welcome"])
142
144
 
145
+ self.loggers = dict() # 所有记录字典
146
+ self.showLog = False # 是否显示记录页
147
+ self.logFileShown = '' # 记录页显示的记录文件名
148
+ self.logSessionBuffer = SessionBuffer()
149
+ self.logSessionBuffer.name = "LOGBUFFER"
150
+
143
151
  self.load_plugins()
144
152
 
145
153
  def initUI(self):
@@ -193,6 +201,7 @@ class PyMudApp:
193
201
  width = D(preferred = Settings.client["naws_width"]),
194
202
  height = D(preferred = Settings.client["naws_height"]),
195
203
  wrap_lines=Condition(lambda: is_true(self.wrap_lines)),
204
+ #left_margins=[NumberedMargin()],
196
205
  #right_margins=[ScrollbarMargin(True)],
197
206
  style="class:text-area"
198
207
  )
@@ -310,8 +319,8 @@ class PyMudApp:
310
319
  ),
311
320
 
312
321
  MenuItem(
313
- "", # 增加一个空名称MenuItem,阻止右侧空白栏点击响应
314
- children=[]
322
+ "", # 增加一个空名称MenuItem,单机后焦点移动至命令行输入处,阻止右侧空白栏点击响应
323
+ handler = lambda : self.app.layout.focus(self.commandLine)
315
324
  )
316
325
  ],
317
326
  floats=[
@@ -332,47 +341,32 @@ class PyMudApp:
332
341
  ss = Settings.sessions
333
342
 
334
343
  for key, site in ss.items():
335
- host = site["host"]
336
- port = site["port"]
337
- encoding = site["encoding"]
338
- autologin = site["autologin"]
339
- scripts = list()
340
- default_script = site["default_script"]
341
-
342
- def_scripts = list()
343
- if isinstance(default_script, str):
344
- def_scripts.extend(default_script.split(","))
345
- elif isinstance(default_script, (list, tuple)):
346
- def_scripts.extend(default_script)
347
-
348
344
  menu = MenuItem(key)
349
- for name, info in site["chars"].items():
350
- after_connect = autologin.format(info[0], info[1])
351
- sess_scripts = list()
352
- sess_scripts.extend(def_scripts)
353
-
354
- if len(info) == 3:
355
- session_script = info[2]
356
- if session_script:
357
- if isinstance(session_script, str):
358
- sess_scripts.extend(session_script.split(","))
359
- elif isinstance(session_script, (list, tuple)):
360
- sess_scripts.extend(session_script)
361
-
362
- sub = MenuItem(name, handler = functools.partial(self.create_session, name, host, port, encoding, after_connect, sess_scripts, info[0]))
345
+ for name in site["chars"].keys():
346
+ sub = MenuItem(name, handler = functools.partial(self._quickHandleSession, key, name))
363
347
  menu.children.append(sub)
364
348
  menus.append(menu)
365
349
 
350
+ menus.append(MenuItem("-", disabled=True))
351
+ menus.append(MenuItem(Settings.text["show_log"], handler = self.show_logSelectDialog))
366
352
  menus.append(MenuItem("-", disabled=True))
367
353
  menus.append(MenuItem(Settings.text["exit"], handler=self.act_exit))
368
354
 
369
355
  return menus
370
356
 
357
+ def invalidate(self):
358
+ "刷新显示界面"
359
+ self.app.invalidate()
360
+
371
361
  def scroll(self, lines = 1):
372
362
  "内容滚动指定行数,小于0为向上滚动,大于0为向下滚动"
373
363
  if self.current_session:
374
364
  s = self.current_session
375
365
  b = s.buffer
366
+ elif self.showLog:
367
+ b = self.logSessionBuffer
368
+
369
+ if isinstance(b, Buffer):
376
370
  if lines < 0:
377
371
  b.cursor_up(-1 * lines)
378
372
  elif lines > 0:
@@ -439,11 +433,22 @@ class PyMudApp:
439
433
  new_key = keys[idx+1]
440
434
  self.activate_session(new_key)
441
435
 
436
+ elif (idx == count -1) and self.showLog:
437
+ self.showLogInTab()
438
+
442
439
  elif event.key_sequence[-1].key == Keys.ControlLeft:
443
440
  if idx > 0:
444
441
  new_key = keys[idx-1]
445
442
  self.activate_session(new_key)
446
443
 
444
+ else:
445
+ if self.showLog:
446
+ if event.key_sequence[-1].key == Keys.ControlLeft:
447
+ keys = list(self.sessions.keys())
448
+ if len(keys) > 0:
449
+ new_key = keys[-1]
450
+ self.activate_session(new_key)
451
+
447
452
  def toggle_mousesupport(self, event: KeyPressEvent):
448
453
  """快捷键F2: 切换鼠标支持状态。用于远程连接时本地复制命令执行操作"""
449
454
  self._mouse_support = not self._mouse_support
@@ -477,25 +482,26 @@ class PyMudApp:
477
482
  line = self.mudFormatProc.line_correction(b.document.current_line)
478
483
  start = max(0, scol)
479
484
  end = min(ecol, len(line))
480
- line_plain = re.sub("\x1b\\[[\d;]+[abcdmz]", "", line, flags = re.IGNORECASE).replace("\r", "").replace("\x00", "")
485
+ line_plain = re.sub(r"\x1b\[[\d;]+[abcdmz]", "", line, flags = re.IGNORECASE).replace("\r", "").replace("\x00", "")
481
486
  #line_plain = re.sub("\x1b\\[[^mz]+[mz]", "", line).replace("\r", "").replace("\x00", "")
482
487
  selection = line_plain[start:end]
483
488
  self.app.clipboard.set_text(selection)
484
489
  self.set_status("已复制:{}".format(selection))
485
-
486
- self.current_session.setVariable("%copy", selection)
490
+ if self.current_session:
491
+ self.current_session.setVariable("%copy", selection)
487
492
  else:
488
493
  # 多行只认行
489
494
  lines = []
490
495
  for row in range(srow, erow + 1):
491
496
  line = b.document.lines[row]
492
- line_plain = re.sub("\x1b\\[[\d;]+[abcdmz]", "", line, flags = re.IGNORECASE).replace("\r", "").replace("\x00", "")
497
+ line_plain = re.sub(r"\x1b\[[\d;]+[abcdmz]", "", line, flags = re.IGNORECASE).replace("\r", "").replace("\x00", "")
493
498
  lines.append(line_plain)
494
499
 
495
500
  self.app.clipboard.set_text("\n".join(lines))
496
501
  self.set_status("已复制:行数{}".format(1 + erow - srow))
497
-
498
- self.current_session.setVariable("%copy", "\n".join(lines))
502
+
503
+ if self.current_session:
504
+ self.current_session.setVariable("%copy", "\n".join(lines))
499
505
 
500
506
  else:
501
507
  # Control-R 复制带有ANSI标记的原始内容(对应字符关系会不正确,因此RAW复制时自动整行复制)
@@ -503,15 +509,18 @@ class PyMudApp:
503
509
  line = b.document.current_line
504
510
  self.app.clipboard.set_text(line)
505
511
  self.set_status("已复制:{}".format(line))
506
-
507
- self.current_session.setVariable("%copy", line)
512
+
513
+ if self.current_session:
514
+ self.current_session.setVariable("%copy", line)
508
515
 
509
516
  else:
510
517
  lines = b.document.lines[srow:erow+1]
511
518
  copy_raw_text = "".join(lines)
512
519
  self.app.clipboard.set_text(copy_raw_text)
513
520
  self.set_status("已复制:行数{}".format(1 + erow - srow))
514
- self.current_session.setVariable("%copy", copy_raw_text)
521
+
522
+ if self.current_session:
523
+ self.current_session.setVariable("%copy", copy_raw_text)
515
524
 
516
525
  # data = self.consoleView.buffer.copy_selection()
517
526
  # self.app.clipboard.set_data(data)
@@ -552,6 +561,66 @@ class PyMudApp:
552
561
 
553
562
  return result
554
563
 
564
+ def show_logSelectDialog(self):
565
+ async def coroutine():
566
+ head_line = " {}{}{}".format('记录文件名'.ljust(15), '文件大小'.rjust(16), '最后修改时间'.center(17))
567
+
568
+ log_list = list()
569
+ files = [f for f in os.listdir('.') if os.path.isfile(f) and f.endswith('.log')]
570
+ for file in files:
571
+ file = os.path.abspath(file)
572
+ filename = os.path.basename(file).ljust(20)
573
+ filesize = f"{os.path.getsize(file):,} Bytes".rjust(20)
574
+ # ctime = datetime.fromtimestamp(os.path.getctime(file)).strftime('%Y-%m-%d %H:%M:%S').rjust(23)
575
+ mtime = datetime.fromtimestamp(os.path.getmtime(file)).strftime('%Y-%m-%d %H:%M:%S').rjust(23)
576
+
577
+ file_display_line = "{}{}{}".format(filename, filesize, mtime)
578
+ log_list.append((file, file_display_line))
579
+
580
+ logDir = os.path.abspath(os.path.join(os.curdir, 'log'))
581
+ if os.path.exists(logDir):
582
+ files = [f for f in os.listdir(logDir) if f.endswith('.log')]
583
+ for file in files:
584
+ file = os.path.join(logDir, file)
585
+ filename = ('log/' + os.path.basename(file)).ljust(20)
586
+ filesize = f"{os.path.getsize(file):,} Bytes".rjust(20)
587
+ # ctime = datetime.fromtimestamp(os.path.getctime(file)).strftime('%Y-%m-%d %H:%M:%S').rjust(23)
588
+ mtime = datetime.fromtimestamp(os.path.getmtime(file)).strftime('%Y-%m-%d %H:%M:%S').rjust(23)
589
+
590
+ file_display_line = "{}{}{}".format(filename, filesize, mtime)
591
+ log_list.append((file, file_display_line))
592
+
593
+ dialog = LogSelectionDialog(
594
+ text = head_line,
595
+ values = log_list
596
+ )
597
+
598
+ result = await self.show_dialog_as_float(dialog)
599
+
600
+ if result:
601
+ self.logFileShown = result
602
+ self.showLogInTab()
603
+
604
+ asyncio.ensure_future(coroutine())
605
+
606
+ def showLogInTab(self):
607
+ "在记录也显示LOG记录"
608
+ self.current_session = None
609
+ self.showLog = True
610
+
611
+ if self.logFileShown:
612
+ filename = os.path.abspath(self.logFileShown)
613
+ if os.path.exists(filename):
614
+ lock = threading.RLock()
615
+ lock.acquire()
616
+ with open(filename, 'r', encoding = 'utf-8', errors = 'ignore') as file:
617
+ self.logSessionBuffer._set_text(file.read())
618
+ lock.release()
619
+
620
+ self.logSessionBuffer.cursor_position = len(self.logSessionBuffer.text)
621
+ self.consoleView.buffer = self.logSessionBuffer
622
+ self.app.invalidate()
623
+
555
624
  def activate_session(self, key):
556
625
  "激活指定名称的session,并将该session设置为当前session"
557
626
  session = self.sessions.get(key, None)
@@ -584,6 +653,7 @@ class PyMudApp:
584
653
  plugin.onSessionDestroy(self.current_session)
585
654
 
586
655
  name = self.current_session.name
656
+ self.current_session.closeLoggers()
587
657
  self.current_session.clean()
588
658
  self.current_session = None
589
659
  self.consoleView.buffer = SessionBuffer()
@@ -627,9 +697,22 @@ class PyMudApp:
627
697
  b.exit_selection()
628
698
  b.cursor_position = len(b.text)
629
699
 
700
+ elif self.showLog:
701
+ b = self.logSessionBuffer
702
+ b.exit_selection()
703
+ b.cursor_position = len(b.text)
704
+
630
705
  def act_close_session(self):
631
706
  "菜单: 关闭当前会话"
632
- self.close_session()
707
+ if self.current_session:
708
+ self.close_session()
709
+
710
+ elif self.showLog:
711
+ self.showLog = False
712
+ self.logSessionBuffer.text = ""
713
+ if len(self.sessions.keys()) > 0:
714
+ new_sess = list(self.sessions.keys())[0]
715
+ self.activate_session(new_sess)
633
716
 
634
717
  def act_echoinput(self):
635
718
  "菜单: 显示/隐藏输入指令"
@@ -722,12 +805,21 @@ class PyMudApp:
722
805
  def btn_title_clicked(self, name, mouse_event: MouseEvent):
723
806
  "顶部会话标签点击切换鼠标事件"
724
807
  if mouse_event.event_type == MouseEventType.MOUSE_UP:
725
- self.activate_session(name)
808
+ if name == '[LOG]':
809
+ self.showLogInTab()
810
+ else:
811
+ self.activate_session(name)
726
812
 
727
813
  def get_frame_title(self):
728
814
  "顶部会话标题选项卡"
729
815
  if len(self.sessions.keys()) == 0:
730
- return Settings.__appname__ + " " + Settings.__version__
816
+ if not self.showLog:
817
+ return Settings.__appname__ + " " + Settings.__version__
818
+ else:
819
+ if self.logFileShown:
820
+ return f'[LOG] {self.logFileShown}'
821
+ else:
822
+ return f'[LOG]'
731
823
 
732
824
  title_formatted_list = []
733
825
  for key, session in self.sessions.items():
@@ -746,6 +838,17 @@ class PyMudApp:
746
838
  title_formatted_list.append((style, key, functools.partial(self.btn_title_clicked, key)))
747
839
  title_formatted_list.append(("", " | "))
748
840
 
841
+ if self.showLog:
842
+ if self.current_session is None:
843
+ style = style = Settings.styles["selected"]
844
+ else:
845
+ style = Settings.styles["normal"]
846
+
847
+ title = f'[LOG] {self.logFileShown}' if self.logFileShown else f'[LOG]'
848
+
849
+ title_formatted_list.append((style, title, functools.partial(self.btn_title_clicked, '[LOG]')))
850
+ title_formatted_list.append(("", " | "))
851
+
749
852
  return title_formatted_list[:-1]
750
853
 
751
854
  def get_statusbar_text(self):
@@ -806,6 +909,50 @@ class PyMudApp:
806
909
  self.status_message = msg
807
910
  self.app.invalidate()
808
911
 
912
+ def _quickHandleSession(self, group, name):
913
+ '''
914
+ 根据指定的组名和会话角色名,从Settings内容,创建一个会话
915
+ '''
916
+ handled = False
917
+ if name in self.sessions.keys():
918
+ self.activate_session(name)
919
+ handled = True
920
+
921
+ else:
922
+ site = Settings.sessions[group]
923
+ if name in site["chars"].keys():
924
+ host = site["host"]
925
+ port = site["port"]
926
+ encoding = site["encoding"]
927
+ autologin = site["autologin"]
928
+ default_script = site["default_script"]
929
+
930
+ def_scripts = list()
931
+ if isinstance(default_script, str):
932
+ def_scripts.extend(default_script.split(","))
933
+ elif isinstance(default_script, (list, tuple)):
934
+ def_scripts.extend(default_script)
935
+
936
+ charinfo = site["chars"][name]
937
+
938
+ after_connect = autologin.format(charinfo[0], charinfo[1])
939
+ sess_scripts = list()
940
+ sess_scripts.extend(def_scripts)
941
+
942
+ if len(charinfo) == 3:
943
+ session_script = charinfo[2]
944
+ if session_script:
945
+ if isinstance(session_script, str):
946
+ sess_scripts.extend(session_script.split(","))
947
+ elif isinstance(session_script, (list, tuple)):
948
+ sess_scripts.extend(session_script)
949
+
950
+ self.create_session(name, host, port, encoding, after_connect, sess_scripts, charinfo[0])
951
+ handled = True
952
+
953
+ return handled
954
+
955
+
809
956
  def handle_session(self, *args):
810
957
  '''
811
958
  嵌入命令 #session 的执行函数,创建一个远程连接会话。
@@ -816,12 +963,18 @@ class PyMudApp:
816
963
  - 当不指定 Encoding: 时, 默认使用utf-8编码
817
964
  - 可以直接使用 #{名称} 切换会话和操作会话命令
818
965
 
966
+ - #session {group}.{name}
967
+ - 相当于直接点击菜单{group}下的{name}菜单来创建会话. 当该会话已存在时,切换到该会话
968
+
819
969
  参数:
820
970
  :name: 会话名称
821
971
  :host: 服务器域名或IP地址
822
972
  :port: 端口号
823
973
  :encoding: 编码格式,不指定时默认为 utf8
824
974
 
975
+ :group: 组名, 即配置文件中, sessions 字段下的某个关键字
976
+ :name: 会话快捷名称, 上述 group 关键字下的 chars 字段中的某个关键字
977
+
825
978
  示例:
826
979
  ``#session {名称} {宿主机} {端口} {编码}``
827
980
  创建一个远程连接会话,使用指定编码格式连接到远程宿主机的指定端口并保存为 {名称} 。其中,编码可以省略,此时使用Settings.server["default_encoding"]的值,默认为utf8
@@ -834,6 +987,9 @@ class PyMudApp:
834
987
  ``#newstart give miui gold``
835
988
  使名称为newstart的会话执行give miui gold指令,但不切换到该会话
836
989
 
990
+ ``#session pkuxkx.newstart``
991
+ 通过指定快捷配置创建会话,相当于点击 世界->pkuxkx->newstart 菜单创建会话。若该会话存在,则切换到该会话
992
+
837
993
  相关命令:
838
994
  - #close
839
995
  - #exit
@@ -841,8 +997,17 @@ class PyMudApp:
841
997
  '''
842
998
 
843
999
  nothandle = True
1000
+ errmsg = "错误的#session命令"
1001
+ if len(args) == 1:
1002
+ host_session = args[0]
1003
+ if '.' in host_session:
1004
+ group, name = host_session.split('.')
1005
+ nothandle = not self._quickHandleSession(group, name)
844
1006
 
845
- if len(args) >= 3:
1007
+ else:
1008
+ errmsg = f'通过单一参数快速创建会话时,要使用 group.name 形式,如 #session pkuxkx.newstart'
1009
+
1010
+ elif len(args) >= 3:
846
1011
  session_name = args[0]
847
1012
  session_host = args[1]
848
1013
  session_port = int(args[2])
@@ -855,7 +1020,7 @@ class PyMudApp:
855
1020
  nothandle = False
856
1021
 
857
1022
  if nothandle:
858
- self.set_status("错误的#session命令")
1023
+ self.set_status(errmsg)
859
1024
 
860
1025
  def enter_pressed(self, buffer: Buffer):
861
1026
  "命令行回车按键处理"
@@ -880,13 +1045,20 @@ class PyMudApp:
880
1045
  self.current_session.writeline("")
881
1046
  else:
882
1047
  try:
1048
+ self.current_session.log.log(f"命令行键入: {cmd_line}\n")
1049
+
883
1050
  cb = CodeBlock(cmd_line)
884
1051
  cb.execute(self.current_session)
885
1052
  except Exception as e:
886
1053
  self.current_session.warning(e)
887
1054
  self.current_session.exec_command(cmd_line)
888
1055
  else:
889
- self.set_status("当前没有正在运行的session.")
1056
+ if cmd_line == "#exit":
1057
+ self.act_exit()
1058
+ elif (cmd_line == "#close") and self.showLog:
1059
+ self.act_close_session()
1060
+ else:
1061
+ self.set_status("当前没有正在运行的session.")
890
1062
 
891
1063
  # 配置:命令行内容保留
892
1064
  if Settings.client["remain_last_input"]:
@@ -981,8 +1153,8 @@ class PyMudApp:
981
1153
  #asyncio.run(self.run_async())
982
1154
 
983
1155
  def get_width(self):
984
- "获取ConsoleView的实际宽度,等于输出宽度-4,(左右线条宽度, 滚动条宽度,右边让出的1列)"
985
- size = self.app.output.get_size().columns - 4
1156
+ "获取ConsoleView的实际宽度,等于输出宽度,(已经没有左右线条和滚动条了)"
1157
+ size = self.app.output.get_size().columns
986
1158
  if Settings.client["status_display"] == 2:
987
1159
  size = size - Settings.client["status_width"] - 1
988
1160
  return size
@@ -1013,7 +1185,7 @@ class PyMudApp:
1013
1185
  self._plugins[plugin.name] = plugin
1014
1186
  plugin.onAppInit(self)
1015
1187
  except Exception as e:
1016
- self.set_status(f"文件: {plugins_dir}\{file} 不是一个合法的插件文件,加载错误,信息为: {e}")
1188
+ self.set_status(f"文件: {plugins_dir}\\{file} 不是一个合法的插件文件,加载错误,信息为: {e}")
1017
1189
 
1018
1190
  # 然后加载当前目录下的插件
1019
1191
  current_dir = os.path.abspath(".")
@@ -1028,8 +1200,7 @@ class PyMudApp:
1028
1200
  self._plugins[plugin.name] = plugin
1029
1201
  plugin.onAppInit(self)
1030
1202
  except Exception as e:
1031
- self.set_status(f"文件: {plugins_dir}\{file} 不是一个合法的插件文件. 加载错误,信息为: {e}")
1032
-
1203
+ self.set_status(f"文件: {plugins_dir}\\{file} 不是一个合法的插件文件. 加载错误,信息为: {e}")
1033
1204
 
1034
1205
  def reload_plugin(self, plugin: Plugin):
1035
1206
  "重新加载指定插件"
@@ -1043,21 +1214,6 @@ class PyMudApp:
1043
1214
  plugin.onSessionCreate(session)
1044
1215
 
1045
1216
 
1046
- def main(cfg_data = None):
1217
+ def startApp(cfg_data = None):
1047
1218
  app = PyMudApp(cfg_data)
1048
1219
  app.run()
1049
-
1050
- if __name__ == "__main__":
1051
-
1052
- cfg = "pymud.cfg"
1053
- import sys
1054
- args = sys.argv
1055
- if len(args) > 1:
1056
- cfg = args[1]
1057
-
1058
- if os.path.exists(cfg):
1059
- with open(cfg, "r", encoding="utf8", errors="ignore") as fp:
1060
- cfg_data = json.load(fp)
1061
- main(cfg_data)
1062
- else:
1063
- main()