src files

This commit is contained in:
Niklas Peng 2025-05-30 19:51:21 +02:00
parent d6579aede2
commit 4f88f30cbb
4 changed files with 2574 additions and 0 deletions

View File

@ -0,0 +1,340 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""File management module for the codeeditor application."""
import logging
import os
import platform
import subprocess
from pathlib import Path
from typing import Any, Dict, List, Optional, Set, Tuple
logger = logging.getLogger(__name__)
class FileManager:
"""Class for managing file operations in the code editor."""
def __init__(self, root_dir: Path):
"""Initialize the FileManager with a root directory.
Args:
root_dir: The root directory to manage files from.
"""
# If root_dir is empty or doesn't exist, use current directory
if not root_dir or not root_dir.exists():
self.root_dir = Path.cwd()
else:
self.root_dir = root_dir
self.code_extensions = [
".py",
".js",
".html",
".css",
".json",
".md",
".txt",
".yaml",
".yml",
".toml",
".sh",
".bat",
".c",
".cpp",
".h",
".hpp",
".java",
".go",
".rs",
".ts",
".tsx",
".jsx",
]
# File extension categories for filtering
self.file_categories = {
"Python": [".py", ".pyw", ".pyc", ".pyd", ".pyo", ".pyi"],
"Web": [".html", ".css", ".js", ".ts", ".jsx", ".tsx", ".vue", ".svelte"],
"Data": [".json", ".csv", ".xml", ".yaml", ".yml", ".toml"],
"Documents": [".md", ".txt", ".pdf", ".docx", ".xlsx", ".pptx"],
"Images": [".jpg", ".jpeg", ".png", ".gif", ".svg", ".ico", ".webp"],
"All Files": ["*"],
}
logger.info(f"FileManager initialized with root directory: {self.root_dir}")
def list_files(
self, directory: Optional[Path] = None, filter_category: str = "All Files"
) -> List[Tuple[str, bool]]:
"""List all files in the given directory.
Args:
directory: The directory to list files from. If None, uses root_dir.
filter_category: Filter files by category.
Returns:
A list of tuples containing (file_path, is_directory).
"""
if directory is None:
directory = self.root_dir
# If directory is empty string or doesn't exist, return empty list
if not directory or not directory.exists():
return []
try:
file_list = []
# Walk through all directories and files recursively
for root, dirs, files in os.walk(directory):
# Skip hidden directories
dirs[:] = [d for d in dirs if not d.startswith(".")]
# Process directories
for dir_name in dirs:
dir_path = Path(root) / dir_name
try:
rel_path = dir_path.relative_to(self.root_dir)
file_list.append((str(rel_path), True))
except ValueError:
# If dir is not relative to root_dir (can happen with symlinks)
continue
# Process files
for file_name in files:
# Skip hidden files except .env
if file_name.startswith(".") and file_name != ".env":
continue
file_path = Path(root) / file_name
try:
rel_path = file_path.relative_to(self.root_dir)
# Apply filter if not "All Files"
if filter_category != "All Files":
if not any(
file_name.endswith(ext)
for ext in self.file_categories.get(filter_category, [])
):
continue
file_list.append((str(rel_path), False))
except ValueError:
# If file is not relative to root_dir (can happen with symlinks)
continue
# Sort the list: directories first, then files, both alphabetically
file_list.sort(key=lambda x: (not x[1], x[0].lower()))
return file_list
except Exception as e:
logger.error(f"Error listing files in {directory}: {e}")
return []
def get_file_tree(
self, expanded_folders: Set[str], filter_category: str = "All Files"
) -> Dict:
"""Get a hierarchical file tree structure.
Args:
expanded_folders: Set of folder paths that are expanded in the UI.
filter_category: Filter files by category.
Returns:
A dictionary representing the file tree structure.
"""
if not self.root_dir or not self.root_dir.exists():
return {}
file_tree = {}
def add_to_tree(path_parts, current_dict, is_dir, full_path):
if not path_parts:
return
part = path_parts[0]
if len(path_parts) == 1:
# Leaf node
if is_dir:
if part not in current_dict:
current_dict[part] = {
"__type": "directory",
"__path": full_path,
"__children": {},
}
else:
current_dict[part] = {"__type": "file", "__path": full_path}
else:
# Directory node
if part not in current_dict:
current_dict[part] = {
"__type": "directory",
"__path": full_path,
"__children": {},
}
add_to_tree(
path_parts[1:], current_dict[part]["__children"], is_dir, full_path
)
for file_path, is_dir in self.list_files(filter_category=filter_category):
path_parts = Path(file_path).parts
add_to_tree(path_parts, file_tree, is_dir, file_path)
return file_tree
def read_file(self, file_path: str) -> Optional[str]:
"""Read the contents of a file.
Args:
file_path: The path to the file to read, relative to root_dir.
Returns:
The contents of the file as a string, or None if the file cannot be read.
"""
try:
full_path = self.root_dir / file_path
with open(full_path, "r", encoding="utf-8") as f:
content = f.read()
logger.info(f"Read file: {file_path}")
return content
except Exception as e:
logger.error(f"Error reading file {file_path}: {e}")
return None
def save_file(self, file_path: str, content: str) -> bool:
"""Save content to a file.
Args:
file_path: The path to the file to save, relative to root_dir.
content: The content to save to the file.
Returns:
True if the file was saved successfully, False otherwise.
"""
try:
full_path = self.root_dir / file_path
# Create parent directories if they don't exist
full_path.parent.mkdir(parents=True, exist_ok=True)
with open(full_path, "w", encoding="utf-8") as f:
f.write(content)
logger.info(f"Saved file: {file_path}")
return True
except Exception as e:
logger.error(f"Error saving file {file_path}: {e}")
return False
def create_directory(self, dir_path: str) -> bool:
"""Create a new directory.
Args:
dir_path: The path to the directory to create, relative to root_dir.
Returns:
True if the directory was created successfully, False otherwise.
"""
try:
full_path = self.root_dir / dir_path
full_path.mkdir(parents=True, exist_ok=True)
logger.info(f"Created directory: {dir_path}")
return True
except Exception as e:
logger.error(f"Error creating directory {dir_path}: {e}")
return False
def delete_item(self, item_path: str) -> bool:
"""Delete a file or directory.
Args:
item_path: The path to the item to delete, relative to root_dir.
Returns:
True if the item was deleted successfully, False otherwise.
"""
try:
full_path = self.root_dir / item_path
if full_path.is_dir():
# Remove directory and all contents
import shutil
shutil.rmtree(full_path)
logger.info(f"Deleted directory: {item_path}")
else:
# Remove file
full_path.unlink()
logger.info(f"Deleted file: {item_path}")
return True
except Exception as e:
logger.error(f"Error deleting item {item_path}: {e}")
return False
def is_code_file(self, file_path: str) -> bool:
"""Check if a file is a code file based on its extension.
Args:
file_path: The path to the file.
Returns:
True if the file is a code file, False otherwise.
"""
return any(file_path.endswith(ext) for ext in self.code_extensions)
def get_file_extension(self, file_path: str) -> str:
"""Get the extension of a file.
Args:
file_path: The path to the file.
Returns:
The file extension.
"""
return os.path.splitext(file_path)[1].lower()
def get_file_icon(self, file_path: str) -> str:
"""Get an icon for a file based on its extension.
Args:
file_path: The path to the file.
Returns:
An emoji icon representing the file type.
"""
ext = self.get_file_extension(file_path)
# Map file extensions to icons
icon_map = {
".py": "🐍", # Python
".js": "📜", # JavaScript
".html": "🌐", # HTML
".css": "🎨", # CSS
".json": "📋", # JSON
".md": "📝", # Markdown
".txt": "📄", # Text
".yml": "⚙️", # YAML
".yaml": "⚙️", # YAML
".toml": "⚙️", # TOML
".sh": "🐚", # Shell
".bat": "🐚", # Batch
".c": "🔧", # C
".cpp": "🔧", # C++
".h": "🔧", # Header
".hpp": "🔧", # C++ Header
".java": "", # Java
".go": "🔵", # Go
".rs": "🦀", # Rust
".ts": "📘", # TypeScript
".jsx": "⚛️", # React JSX
".tsx": "⚛️", # React TSX
}
return icon_map.get(ext, "📄") # Default to generic file icon
def get_file_categories(self) -> Dict[str, List[str]]:
"""Get file categories for filtering.
Returns:
Dictionary of file categories and their extensions.
"""
return self.file_categories

View File

@ -0,0 +1,179 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Search management module for the codeeditor application."""
import logging
import os
from typing import Dict, List, Optional
import requests
from dotenv import load_dotenv
# Load environment variables from .env file
load_dotenv()
logger = logging.getLogger(__name__)
class SearchManager:
"""Class for managing internet searches."""
def __init__(self):
"""Initialize the SearchManager."""
self.api_key = os.getenv("SEARCH_API_KEY")
self.search_engine_id = os.getenv("SEARCH_ENGINE_ID")
logger.info("SearchManager initialized")
def search(self, query: str, num_results: int = 5) -> List[Dict[str, str]]:
"""Perform an internet search for the given query.
Args:
query: The search query.
num_results: The number of results to return.
Returns:
A list of dictionaries containing search results.
"""
results = []
try:
# Use Google Custom Search API if API key is available
if self.api_key and self.search_engine_id:
results = self._google_search(query, num_results)
else:
# Fallback to a simple search simulation
logger.warning(
"Search API keys not found. Using simulated search results."
)
results = self._simulated_search(query)
logger.info(f"Search completed for query: {query}")
return results
except Exception as e:
logger.error(f"Error performing search: {e}")
return [
{
"title": "Search Error",
"link": "",
"snippet": f"Error performing search: {str(e)}",
}
]
def _google_search(self, query: str, num_results: int = 5) -> List[Dict[str, str]]:
"""Perform a search using Google Custom Search API.
Args:
query: The search query.
num_results: The number of results to return.
Returns:
A list of dictionaries containing search results.
"""
url = "https://www.googleapis.com/customsearch/v1"
params = {
"key": self.api_key,
"cx": self.search_engine_id,
"q": query,
"num": min(num_results, 10), # API limit is 10
}
response = requests.get(url, params=params)
data = response.json()
results = []
if "items" in data:
for item in data["items"]:
results.append(
{
"title": item.get("title", ""),
"link": item.get("link", ""),
"snippet": item.get("snippet", ""),
}
)
return results
def _simulated_search(self, query: str) -> List[Dict[str, str]]:
"""Simulate search results when API keys are not available.
Args:
query: The search query.
Returns:
A list of dictionaries containing simulated search results.
"""
# This is a fallback method that returns simulated results
# In a real application, you might want to use a free alternative or web scraping
# with proper rate limiting and respect for robots.txt
# Always include these informational results
results = [
{
"title": "Search API Key Required",
"link": "https://developers.google.com/custom-search/v1/overview",
"snippet": "To enable real search functionality, please add SEARCH_API_KEY and SEARCH_ENGINE_ID to your .env file.",
},
{
"title": f"Search Results for: {query}",
"link": f"https://www.google.com/search?q={query.replace(' ', '+')}",
"snippet": "Click this link to perform the search manually in your browser.",
},
]
# Add some simulated results based on common programming queries
query_lower = query.lower()
if "python" in query_lower:
results.extend(
[
{
"title": "Python Documentation",
"link": "https://docs.python.org/3/",
"snippet": "Python 3 documentation. Welcome! This is the official documentation for Python 3. Parts of the documentation: What's new in Python 3? Or all 'What's new' documents since 2.0.",
},
{
"title": "Python Tutorial - W3Schools",
"link": "https://www.w3schools.com/python/",
"snippet": "Python is a popular programming language. Python can be used on a server to create web applications. Start learning Python now.",
},
{
"title": "The Python Standard Library",
"link": "https://docs.python.org/3/library/",
"snippet": "The Python Standard Library. While The Python Language Reference describes the exact syntax and semantics of the Python language, this library reference manual describes the standard library that is distributed with Python.",
},
]
)
elif "javascript" in query_lower:
results.extend(
[
{
"title": "JavaScript - MDN Web Docs",
"link": "https://developer.mozilla.org/en-US/docs/Web/JavaScript",
"snippet": "JavaScript (JS) is a lightweight, interpreted, or just-in-time compiled programming language with first-class functions.",
},
]
)
elif "streamlit" in query_lower:
results.extend(
[
{
"title": "Streamlit Documentation",
"link": "https://docs.streamlit.io/",
"snippet": "Streamlit is an open-source Python library that makes it easy to create and share beautiful, custom web apps for machine learning and data science.",
},
]
)
# Add a generic result for any query to ensure we always return something
if len(results) <= 2:
results.append(
{
"title": f"{query} - Programming Resources",
"link": f"https://github.com/search?q={query.replace(' ', '+')}",
"snippet": f"Find open-source projects and resources related to {query} on GitHub.",
}
)
return results

View File

@ -0,0 +1,213 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""System prompt generation module for the codeeditor application."""
import logging
import os
from pathlib import Path
from typing import Dict, List, Optional
logger = logging.getLogger(__name__)
class SystemPrompter:
"""Class for generating system prompts for the AI assistant."""
def __init__(self):
"""Initialize the SystemPrompter."""
self.base_prompt = (
"You are an AI coding assistant helping with programming tasks. "
"You provide clear, concise, and accurate code suggestions, explanations, "
"and debugging help. Your responses should be helpful, informative, and "
"focused on solving the user's coding problems.\n\n"
"When providing code examples, always format them with markdown code blocks "
"with the appropriate language syntax highlighting. For example:\n"
"```python\n"
"def hello_world():\n"
" print('Hello, World!')\n"
"```\n\n"
"When suggesting changes to existing code, clearly indicate which parts should "
"be modified, added, or removed."
)
logger.info("SystemPrompter initialized")
def generate_prompt(self, file_context: Optional[str] = None) -> str:
"""Generate a system prompt for the AI assistant.
Args:
file_context: Optional context from the current file.
Returns:
A system prompt string.
"""
prompt = self.base_prompt
if file_context:
# Add file context to the prompt
file_path = file_context.split("\n", 1)[0].replace("File: ", "")
file_content = (
file_context.split("\n", 1)[1] if "\n" in file_context else ""
)
prompt += (
f"\n\nHere is the current file content that the user is working with:\n"
f"File: {file_path}\n"
)
prompt += "```\n" + file_content + "\n```\n"
# Add file type specific context
file_ext = Path(file_path).suffix.lower()
if file_ext:
language_context = self._get_language_context(file_ext)
if language_context:
prompt += f"\n{language_context}\n"
prompt += "\nPlease consider this context when providing assistance."
return prompt
def _get_language_context(self, file_extension: str) -> str:
"""Get language-specific context based on file extension.
Args:
file_extension: The file extension including the dot.
Returns:
Language-specific context string.
"""
language_contexts = {
".py": (
"This is a Python file. When suggesting improvements, consider Python best practices "
"such as PEP 8 style guidelines, type hints, docstrings, and efficient use of "
"Python's standard library and idioms."
),
".js": (
"This is a JavaScript file. Consider modern JavaScript (ES6+) features, "
"asynchronous patterns, and clean code practices when providing suggestions."
),
".html": (
"This is an HTML file. Consider semantic HTML5 elements, accessibility best practices, "
"and proper document structure when providing suggestions."
),
".css": (
"This is a CSS file. Consider responsive design principles, browser compatibility, "
"and modern CSS features when providing suggestions."
),
".jsx": (
"This is a React JSX file. Consider React best practices, component structure, "
"hooks usage, and state management when providing suggestions."
),
".tsx": (
"This is a TypeScript React file. Consider TypeScript type safety, React best practices, "
"component structure, hooks usage, and state management when providing suggestions."
),
}
return language_contexts.get(file_extension, "")
def generate_code_improvement_prompt(
self, code: str, file_path: Optional[str] = None
) -> str:
"""Generate a prompt for code improvement suggestions.
Args:
code: The code to improve.
file_path: Optional path to the file.
Returns:
A system prompt string for code improvement.
"""
prompt = self.base_prompt
prompt += "\n\nPlease review the following code and suggest improvements "
prompt += "for readability, efficiency, and best practices:\n"
if file_path:
prompt += f"File: {file_path}\n"
# Add language-specific context
file_ext = Path(file_path).suffix.lower()
if file_ext:
language_context = self._get_language_context(file_ext)
if language_context:
prompt += f"\n{language_context}\n"
prompt += "```\n" + code + "\n```\n"
prompt += "\nPlease provide specific suggestions with examples."
return prompt
def generate_debugging_prompt(
self, code: str, error_message: str, file_path: Optional[str] = None
) -> str:
"""Generate a prompt for debugging assistance.
Args:
code: The code to debug.
error_message: The error message to debug.
file_path: Optional path to the file.
Returns:
A system prompt string for debugging.
"""
prompt = self.base_prompt
prompt += (
"\n\nPlease help debug the following code that is producing an error:\n"
)
if file_path:
prompt += f"File: {file_path}\n"
# Add language-specific context
file_ext = Path(file_path).suffix.lower()
if file_ext:
language_context = self._get_language_context(file_ext)
if language_context:
prompt += f"\n{language_context}\n"
prompt += "```\n" + code + "\n```\n"
prompt += "\nError message:\n"
prompt += "```\n" + error_message + "\n```\n"
prompt += (
"\nPlease explain what's causing the error and suggest a fix. "
"Include a corrected version of the code."
)
return prompt
def generate_completion_prompt(
self, code: str, cursor_position: int, file_path: Optional[str] = None
) -> str:
"""Generate a prompt for code completion.
Args:
code: The code to complete.
cursor_position: The position of the cursor in the code.
file_path: Optional path to the file.
Returns:
A system prompt string for code completion.
"""
prompt = self.base_prompt
prompt += "\n\nPlease help complete the following code at the cursor position (marked by |):\n"
if file_path:
prompt += f"File: {file_path}\n"
# Add language-specific context
file_ext = Path(file_path).suffix.lower()
if file_ext:
language_context = self._get_language_context(file_ext)
if language_context:
prompt += f"\n{language_context}\n"
# Insert cursor marker
code_with_cursor = code[:cursor_position] + "|" + code[cursor_position:]
prompt += "```\n" + code_with_cursor + "\n```\n"
prompt += (
"\nPlease suggest code completions that would make sense at the cursor position. "
"Provide a few different options if appropriate."
)
return prompt

File diff suppressed because it is too large Load Diff