Files
android-backup-gui/ktlint.py
sakuradairong 818faefa86 release: v1.15
修复: 备份异常 EPERM (Operation not permitted) 导致整个备份中断
- BackupOperation: 检查 mkdirs 返回值,writeText 异常时优雅降级而非抛出
- BackupScreen: 增加异常完整堆栈记录和智能提示(EPERM/EACCES)
2026-06-08 14:18:28 +08:00

150 lines
5.2 KiB
Python
Executable File

#!/usr/bin/env python3
"""Kotlin LSP client for code diagnostics. Collects all LSP messages."""
import subprocess, json, sys, os, signal, time
from pathlib import Path
def run_diagnostics(project_dir: str, file_path: str, timeout: int = 60):
proc = subprocess.Popen(
['/usr/local/bin/kotlin-language-server'],
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
cwd=project_dir, preexec_fn=lambda: signal.signal(signal.SIGPIPE, signal.SIG_DFL)
)
def send(msg):
data = json.dumps(msg).encode('utf-8')
proc.stdin.write(f'Content-Length: {len(data)}\r\n\r\n'.encode('utf-8'))
proc.stdin.write(data)
proc.stdin.flush()
def recv(timeout_s=5):
content_length = 0
end = time.time() + timeout_s
while time.time() < end:
if proc.poll() is not None:
return None
ready = proc.stdout.readable()
if not ready:
time.sleep(0.05)
continue
line = proc.stdout.readline()
if not line:
time.sleep(0.05)
continue
line = line.decode('utf-8', errors='replace').strip()
if line.startswith('Content-Length:'):
content_length = int(line.split(':')[1].strip())
elif line == '' and content_length > 0:
body = proc.stdout.read(content_length).decode('utf-8', errors='replace')
return json.loads(body)
return 'TIMEOUT'
all_msgs = []
send({
'jsonrpc': '2.0', 'id': 1, 'method': 'initialize',
'params': {
'processId': os.getpid(),
'capabilities': {
'textDocument': {'diagnostics': {'dynamicRegistration': False}},
'workspace': {'didChangeWatchedFiles': {'dynamicRegistration': False}}
},
'rootUri': f'file://{project_dir}',
'workspaceFolders': [{'uri': f'file://{project_dir}', 'name': Path(project_dir).name}]
}
})
# Read all messages until we get initialize result
end = time.time() + timeout
init_ok = False
while time.time() < end and not init_ok:
msg = recv(5)
if msg is None:
break
if msg == 'TIMEOUT':
continue
all_msgs.append(('init', msg))
if msg.get('id') == 1 and 'result' in msg:
init_ok = True
if not init_ok:
return all_msgs, f'INIT_TIMEOUT after {timeout}s'
send({'jsonrpc': '2.0', 'method': 'initialized', 'params': {}})
# Open file
file_uri = f'file://{file_path}'
with open(file_path) as f:
content = f.read()
send({
'jsonrpc': '2.0', 'method': 'textDocument/didOpen',
'params': {
'textDocument': {
'uri': file_uri, 'languageId': 'kotlin',
'version': 1, 'text': content
}
}
})
# Collect messages for remaining time
end = time.time() + 30
while time.time() < end:
msg = recv(3)
if msg is None or msg == 'TIMEOUT':
continue
all_msgs.append(('open', msg))
# Shutdown
send({'jsonrpc': '2.0', 'id': 2, 'method': 'shutdown', 'params': {}})
try:
proc.terminate()
proc.wait(timeout=3)
except:
proc.kill()
return all_msgs, 'OK'
if __name__ == '__main__':
file_path = os.path.abspath(sys.argv[1])
project_dir = os.path.abspath(sys.argv[2]) if len(sys.argv) > 2 else os.getcwd()
print(f'Project: {project_dir}')
print(f'File: {file_path}\n')
msgs, status = run_diagnostics(project_dir, file_path)
print(f'Status: {status}')
print(f'Messages received: {len(msgs)}\n')
diag_count = 0
for phase, msg in msgs:
method = msg.get('method', '?')
if 'id' in msg:
method = f'response(id={msg["id"]})'
if 'error' in msg:
print(f' [{phase}] {method} ERROR: {msg["error"]}')
elif method == 'window/logMessage':
print(f' [{phase}] log: {msg.get("params",{}).get("message","")}')
elif method == 'window/showMessage':
print(f' [{phase}] show: {msg.get("params",{}).get("message","")}')
elif method == 'textDocument/publishDiagnostics':
diags = msg.get('params', {}).get('diagnostics', [])
diag_count += len(diags)
uri = msg.get('params', {}).get('uri', '')
print(f' [{phase}] publishDiagnostics ({len(diags)} items): {os.path.basename(uri)}')
for d in diags:
r = d.get('range', {})
s = r.get('start', {})
sev = {1:'E',2:'W',3:'I',4:'H'}.get(d.get('severity'),'?')
print(f' {sev} {s.get("line",0)+1}:{s.get("character",0)+1} {d.get("message","")}')
elif method.startswith('response'):
if 'result' in msg:
caps = msg.get('result', {}).get('capabilities', {})
print(f' [{phase}] {method} capabilities: {json.dumps(caps, indent=2)[:400]}')
else:
print(f' [{phase}] {method}')
else:
print(f' [{phase}] {method}: {json.dumps(msg, indent=2)[:200]}')
print(f'\nTotal diagnostics: {diag_count}')