feat: pylsp 作为工具之一 + AI 写/改 .py 自动触发诊断并注入上下文
- 补装 pylsp 及 lint 后端(pyflakes/pycodestyle/rope/mccabe),真实诊断可用 - code_editor / file_manager 写/改操作后自动调用 LSP 诊断(.py),结果经 get_lsp_context() 静默注入下一轮模型上下文,仅在有问题时的可见提示 - config.json 中 rust 设为 disabled(本机未装,免 ERROR 噪声) - requirements.txt / config.json.example 补充 pylsp 配置说明 - 新增 4 个 _auto_lsp_check 钩子单测
This commit is contained in:
1 parent
c655d61af4
commit
1054932921
5 files changed
+183
-39
No files matched your search
@@ -290,3 +290,86 @@ def test_fc_tools_includes_lsp(tmp_path, monkeypatch, fake_server):
|
||||
assert "file" in diag["function"]["parameters"]["properties"]
|
||||
finally:
|
||||
mgr.shutdown()
|
||||
|
||||
|
||||
# ── code_editor / file_manager 的「写 .py 后自动触发 pylsp 诊断」钩子 ──
|
||||
def _make_fake_lsp_manager(diag_text):
|
||||
"""构造一个 fake LSP 管理器,记录被调用,并返回固定诊断文本。"""
|
||||
calls = []
|
||||
|
||||
class FakeManager:
|
||||
def __init__(self):
|
||||
self.clients = {"python": object()} # 模拟已连接 python server
|
||||
|
||||
def diagnostics(self, filepath, wait=1.5):
|
||||
calls.append(filepath)
|
||||
return diag_text
|
||||
|
||||
fm = FakeManager()
|
||||
fm._calls = calls
|
||||
return fm
|
||||
|
||||
|
||||
def _attach(cli, fm):
|
||||
class UTM:
|
||||
_lsp_manager = fm
|
||||
cli.liugin_manager = UTM()
|
||||
|
||||
|
||||
def test_auto_lsp_check_triggers_on_py_write(tmp_path):
|
||||
"""写/改 .py 后钩子应调用 LSP 诊断,并在有问题时返回概要。"""
|
||||
from plugins.code_editor import Liugin as CodeEditor
|
||||
ce = CodeEditor()
|
||||
fm = _make_fake_lsp_manager("🔍 x.py:2 条诊断\n 错误 L1:1 — undefined name 'foo'")
|
||||
cli = type("CLI", (), {})()
|
||||
_attach(cli, fm)
|
||||
ce.set_cli(cli)
|
||||
|
||||
py_file = tmp_path / "mod.py"
|
||||
py_file.write_text("foo = bar\n", encoding="utf-8")
|
||||
out = ce._auto_lsp_check(str(py_file))
|
||||
assert str(py_file) in fm._calls, "应调用 lsp_manager.diagnostics(该 py 文件)"
|
||||
assert "LSP 诊断" in out and "2 条诊断" in out
|
||||
|
||||
|
||||
def test_auto_lsp_check_silent_when_clean(tmp_path):
|
||||
"""文件干净(无诊断)时钩子应静默返回空串。"""
|
||||
from plugins.code_editor import Liugin as CodeEditor
|
||||
ce = CodeEditor()
|
||||
fm = _make_fake_lsp_manager("✅ mod.py:无诊断(未发现问题)")
|
||||
cli = type("CLI", (), {})()
|
||||
_attach(cli, fm)
|
||||
ce.set_cli(cli)
|
||||
|
||||
py_file = tmp_path / "clean.py"
|
||||
py_file.write_text("x = 1\n", encoding="utf-8")
|
||||
assert ce._auto_lsp_check(str(py_file)) == ""
|
||||
|
||||
|
||||
def test_auto_lsp_check_silent_without_lsp_manager(tmp_path):
|
||||
"""未连接 LSP 管理器(或未配置 server)时钩子应静默跳过,绝不报错。"""
|
||||
from plugins.code_editor import Liugin as CodeEditor
|
||||
ce = CodeEditor()
|
||||
cli = type("CLI", (), {})()
|
||||
# liugin_manager 没有 _lsp_manager 属性
|
||||
cli.liugin_manager = type("UTM", (), {})()
|
||||
ce.set_cli(cli)
|
||||
|
||||
py_file = tmp_path / "any.py"
|
||||
py_file.write_text("pass\n", encoding="utf-8")
|
||||
assert ce._auto_lsp_check(str(py_file)) == ""
|
||||
|
||||
|
||||
def test_auto_lsp_check_no_server_for_ext(tmp_path):
|
||||
"""扩展名未配置 LSP server 时(如 .txt)应静默返回空串。"""
|
||||
from plugins.code_editor import Liugin as CodeEditor
|
||||
ce = CodeEditor()
|
||||
fm = _make_fake_lsp_manager("⚠️ 没有为 x.txt 配置 LSP server")
|
||||
# clients 里只有 python,没有 txt 对应的 server;这里用文本模拟「无 server」返回
|
||||
cli = type("CLI", (), {})()
|
||||
_attach(cli, fm)
|
||||
ce.set_cli(cli)
|
||||
|
||||
txt_file = tmp_path / "note.txt"
|
||||
txt_file.write_text("hello\n", encoding="utf-8")
|
||||
assert ce._auto_lsp_check(str(txt_file)) == ""
|
||||
Reference in new issue
Block a user