diff --git a/README.md b/README.md index c0395a2e05..d5a5deac25 100644 --- a/README.md +++ b/README.md @@ -799,6 +799,46 @@ $ telnet 127.0.0.1 6899 in another terminal, where `127.0.0.1` is the IP address and `6899` is port you find in `./debug.log`. +#### Remote debugging example + +Here's a complete example of how to debug a specific issue: + +1. Let's say you suspect an issue when sending messages. Find the function in the codebase that handles this (e.g., in `zulipterminal/ui/ui.py`). + +2. Add the debugger statement just before the suspicious code: + ```python + def send_message(self): + from pudb.remote import set_trace + set_trace() # This will pause execution here + # Rest of the function... + ``` + +3. Run Zulip Terminal with debug mode enabled: + ```bash + zulip-term -d + ``` + +4. When you trigger the send_message function, check debug.log for telnet connection details: + ```bash + tail -f debug.log + ``` + +5. Connect with telnet and you'll get an interactive debugger to step through the code. + +#### Profiling for performance issues + +If you're experiencing performance problems, you can run Zulip Terminal with profiling enabled: + +```bash +zulip-term --profile +``` + +This will create a profile output file which you can analyze using: + +```bash +snakeviz zulip-terminal.prof +``` + #### There's no effect in Zulip Terminal after making local changes! This likely means that you have installed both normal and development versions diff --git a/makefile b/makefile index 13119cbe59..5759ebb918 100644 --- a/makefile +++ b/makefile @@ -1,4 +1,4 @@ -.PHONY: install-devel check lint test check-clean-tree fix force-fix venv +.PHONY: install-devel check lint test check-clean-tree fix force-fix venv debug debug-profile debug-clean # NOTE: ZT_VENV and BASEPYTHON are advanced undocumented features # Customize your venv name by running make as "ZT_VENV=my_venv_name make " @@ -24,6 +24,20 @@ lint: venv test: venv @pytest +### DEBUG TARGETS ### + +debug: venv + @echo "=== Running with debug enabled ===" + $(PYTHON) -m zulipterminal.cli.run -d + +debug-profile: venv + @echo "=== Running with profiling enabled ===" + $(PYTHON) -m zulipterminal.cli.run --profile + +debug-clean: + @echo "=== Cleaning debug files ===" + rm -f debug.log zulip-terminal.prof zulip-terminal-tracebacks.log + ### FIX FILES ### check-clean-tree: diff --git a/setup.py b/setup.py index 168f33dd33..85701dfa50 100644 --- a/setup.py +++ b/setup.py @@ -54,6 +54,7 @@ def long_description(): helper_deps = [ "pudb==2022.1.1", "snakeviz>=2.1.1", + "requests>=2.25.0", # Added for debug_helper.py ] setup( @@ -93,6 +94,8 @@ def long_description(): "console_scripts": [ "zulip-term = zulipterminal.cli.run:main", "zulip-term-check-symbols = zulipterminal.scripts.render_symbols:main", + # Added debug helper with proper path + "zulip-term-debug = zulipterminal.scripts.debug_helper:main", ], }, extras_require={ diff --git a/zulipterminal/scripts/debug_helper.py b/zulipterminal/scripts/debug_helper.py new file mode 100644 index 0000000000..73ddd007a2 --- /dev/null +++ b/zulipterminal/scripts/debug_helper.py @@ -0,0 +1,164 @@ +#!/usr/bin/env python3 +""" +Helper script for debugging Zulip Terminal. + +This script provides utilities for common debugging tasks: +1. Analyzing debug logs +2. Testing connectivity to Zulip server +3. Checking terminal capabilities +""" + +import argparse +import json +import logging +import os +import re +import subprocess +from typing import Optional + + +# Configure logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + + +def analyze_debug_log(log_file: str = "debug.log") -> None: + """ + Analyze a debug log file for common issues. + """ + if not os.path.exists(log_file): + logger.error("Log file '%s' not found", log_file) + return + + logger.info("Analyzing %s...", log_file) + with open(log_file, "r") as f: + content = f.read() + + # Look for error patterns + error_patterns = [r"ERROR", r"Exception", r"Traceback", r"Failed to"] + + errors_found = False + for pattern in error_patterns: + matches = re.finditer(pattern, content, re.IGNORECASE) + for match in matches: + line_start = content.rfind("\n", 0, match.start()) + 1 + line_end = content.find("\n", match.end()) + if line_end == -1: + line_end = len(content) + + line = content[line_start:line_end].strip() + logger.warning("Potential issue found: %s", line) + errors_found = True + + if not errors_found: + logger.info("No obvious errors found in the log file.") + + +def test_connectivity(server_url: Optional[str] = None) -> None: + """ + Test connectivity to a Zulip server. + """ + if not server_url: + # Try to get server URL from zuliprc + zuliprc_path = os.path.expanduser("~/.zuliprc") + if os.path.exists(zuliprc_path): + with open(zuliprc_path, "r") as f: + for line in f: + if line.startswith("site="): + server_url = line.split("=")[1].strip() + break + + if not server_url: + logger.error("No server URL provided and couldn't find one in ~/.zuliprc") + return + + logger.info("Testing connectivity to %s...", server_url) + try: + import requests + + response = requests.get(f"{server_url}/api/v1/server_settings") + if response.status_code == 200: + logger.info("Successfully connected to %s", server_url) + try: + settings = response.json() + logger.info( + "Server version: %s", settings.get("zulip_version", "unknown") + ) + except json.JSONDecodeError: + logger.error("Received response, but couldn't parse as JSON") + else: + logger.error("Failed to connect: HTTP status %s", response.status_code) + except Exception as e: + logger.error("Connection error: %s", e) + + +def check_terminal_capabilities() -> None: + """ + Check for terminal capabilities that might affect Zulip Terminal. + """ + logger.info("Checking terminal capabilities...") + + # Check for color support + colors = os.environ.get("TERM", "unknown") + logger.info("TERM environment: %s", colors) + + if "COLORTERM" in os.environ: + logger.info("COLORTERM: %s", os.environ["COLORTERM"]) + + # Check for Unicode support + logger.info("Testing Unicode rendering capabilities:") + test_chars = [ + ("Basic symbols", "▶ ◀ ✓ ✗"), + ("Emoji (simple)", "😀 🙂 👍"), + ("Box drawing", "│ ┌ ┐ └ ┘ ├ ┤ ┬ ┴ ┼"), + ("Math symbols", "∞ ∑ √ ∫ π"), + ] + + for name, chars in test_chars: + logger.info(" %s: %s", name, chars) + + +def main() -> None: + """ + Main entry point for the debugging helper. + """ + parser = argparse.ArgumentParser(description="Zulip Terminal Debugging Helper") + subparsers = parser.add_subparsers(dest="command", help="Command to run") + + # Log analyzer + log_parser = subparsers.add_parser("log", help="Analyze debug logs") + log_parser.add_argument("--file", default="debug.log", help="Log file to analyze") + + # Connectivity test + conn_parser = subparsers.add_parser("connect", help="Test connectivity") + conn_parser.add_argument( + "--server", help="Server URL (e.g., https://chat.zulip.org)" + ) + + # Terminal test + subparsers.add_parser("terminal", help="Check terminal capabilities") + + # Run zulip-term with debug + run_parser = subparsers.add_parser("run", help="Run zulip-term with debugging") + run_parser.add_argument("--profile", action="store_true", help="Enable profiling") + + args = parser.parse_args() + + if args.command == "log": + analyze_debug_log(args.file) + elif args.command == "connect": + test_connectivity(args.server) + elif args.command == "terminal": + check_terminal_capabilities() + elif args.command == "run": + cmd = ["zulip-term", "-d"] + if args.profile: + cmd.append("--profile") + logger.info("Running: %s", " ".join(cmd)) + subprocess.run(cmd, check=False) + else: + parser.print_help() + + +if __name__ == "__main__": + main()