aivim 0.1.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.
- aivim/__init__.py +11 -0
- aivim/ai_service.py +457 -0
- aivim/buffer.py +120 -0
- aivim/command_handler.py +442 -0
- aivim/commands.py +285 -0
- aivim/display.py +650 -0
- aivim/editor.py +1928 -0
- aivim/history.py +139 -0
- aivim/key_handler.py +296 -0
- aivim/modes.py +12 -0
- aivim/syntax.py +131 -0
- aivim/ui.py +208 -0
- aivim/utils.py +158 -0
- aivim-0.1.0.dist-info/METADATA +285 -0
- aivim-0.1.0.dist-info/RECORD +19 -0
- aivim-0.1.0.dist-info/WHEEL +5 -0
- aivim-0.1.0.dist-info/entry_points.txt +2 -0
- aivim-0.1.0.dist-info/licenses/LICENSE +676 -0
- aivim-0.1.0.dist-info/top_level.txt +1 -0
aivim/__init__.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AIVim - An AI-enhanced version of Vim
|
|
3
|
+
"""
|
|
4
|
+
from .editor import Editor
|
|
5
|
+
from .buffer import Buffer
|
|
6
|
+
from .display import Display
|
|
7
|
+
from .command_handler import CommandHandler
|
|
8
|
+
from .ai_service import AIService
|
|
9
|
+
from .history import History
|
|
10
|
+
|
|
11
|
+
__version__ = "0.1.0"
|
aivim/ai_service.py
ADDED
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AI services for AIVim using multiple AI providers including OpenAI, Anthropic, and local LLMs
|
|
3
|
+
"""
|
|
4
|
+
import logging
|
|
5
|
+
import os
|
|
6
|
+
import time
|
|
7
|
+
from typing import Optional, Dict, Any
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
from openai import OpenAI
|
|
11
|
+
OPENAI_AVAILABLE = True
|
|
12
|
+
except ImportError:
|
|
13
|
+
OPENAI_AVAILABLE = False
|
|
14
|
+
|
|
15
|
+
try:
|
|
16
|
+
from llama_cpp import Llama
|
|
17
|
+
LLAMA_AVAILABLE = True
|
|
18
|
+
except ImportError:
|
|
19
|
+
LLAMA_AVAILABLE = False
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class AIService:
|
|
23
|
+
"""
|
|
24
|
+
Service for interacting with AI models
|
|
25
|
+
"""
|
|
26
|
+
def __init__(self):
|
|
27
|
+
"""Initialize AI service"""
|
|
28
|
+
# OpenAI setup
|
|
29
|
+
self.openai_api_key = os.environ.get("OPENAI_API_KEY")
|
|
30
|
+
self.openai_client = None
|
|
31
|
+
|
|
32
|
+
# Anthropic setup
|
|
33
|
+
self.anthropic_api_key = os.environ.get("ANTHROPIC_API_KEY")
|
|
34
|
+
self.anthropic_client = None
|
|
35
|
+
|
|
36
|
+
# Local LLM setup
|
|
37
|
+
self.llama_model_path = os.environ.get("LLAMA_MODEL_PATH")
|
|
38
|
+
self.local_llm = None
|
|
39
|
+
|
|
40
|
+
# Default model provider
|
|
41
|
+
self.current_model = "openai" # Options: "openai", "claude", "local"
|
|
42
|
+
|
|
43
|
+
# Initialize available clients
|
|
44
|
+
self._initialize_clients()
|
|
45
|
+
|
|
46
|
+
def _initialize_clients(self):
|
|
47
|
+
"""Initialize available AI clients based on API keys"""
|
|
48
|
+
# Initialize OpenAI
|
|
49
|
+
if OPENAI_AVAILABLE:
|
|
50
|
+
if self.openai_api_key:
|
|
51
|
+
try:
|
|
52
|
+
self.openai_client = OpenAI(api_key=self.openai_api_key)
|
|
53
|
+
logging.info("OpenAI client initialized successfully")
|
|
54
|
+
except Exception as e:
|
|
55
|
+
logging.error(f"Error initializing OpenAI client: {str(e)}")
|
|
56
|
+
else:
|
|
57
|
+
logging.warning("OPENAI_API_KEY environment variable not set. OpenAI features will not work.")
|
|
58
|
+
else:
|
|
59
|
+
logging.warning("OpenAI package not installed. OpenAI features will not work.")
|
|
60
|
+
|
|
61
|
+
# Initialize Anthropic
|
|
62
|
+
try:
|
|
63
|
+
import anthropic
|
|
64
|
+
if self.anthropic_api_key:
|
|
65
|
+
try:
|
|
66
|
+
self.anthropic_client = anthropic.Anthropic(api_key=self.anthropic_api_key)
|
|
67
|
+
logging.info("Anthropic client initialized successfully")
|
|
68
|
+
except Exception as e:
|
|
69
|
+
logging.error(f"Error initializing Anthropic client: {str(e)}")
|
|
70
|
+
else:
|
|
71
|
+
logging.warning("ANTHROPIC_API_KEY environment variable not set. Claude features will not work.")
|
|
72
|
+
except ImportError:
|
|
73
|
+
logging.warning("Anthropic package not installed. Claude features will not work.")
|
|
74
|
+
|
|
75
|
+
# Initialize Local LLM (llama.cpp)
|
|
76
|
+
if LLAMA_AVAILABLE:
|
|
77
|
+
# Try to load a default model if path is not provided
|
|
78
|
+
model_path = self.llama_model_path
|
|
79
|
+
if not model_path:
|
|
80
|
+
# First check for models in common locations
|
|
81
|
+
possible_paths = [
|
|
82
|
+
os.path.expanduser("~/.local/share/llama.cpp/models"),
|
|
83
|
+
os.path.expanduser("~/models"),
|
|
84
|
+
"./models"
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
# Names of popular open models to check for
|
|
88
|
+
model_names = [
|
|
89
|
+
"llama-2-7b-chat.gguf",
|
|
90
|
+
"ggml-model-q4_0.bin",
|
|
91
|
+
"mistral-7b-instruct-v0.1.Q4_0.gguf",
|
|
92
|
+
"llama-2-13b-chat.gguf"
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
# Search for models
|
|
96
|
+
for path in possible_paths:
|
|
97
|
+
if os.path.exists(path):
|
|
98
|
+
for model in model_names:
|
|
99
|
+
model_path = os.path.join(path, model)
|
|
100
|
+
if os.path.exists(model_path):
|
|
101
|
+
logging.info(f"Found local model: {model_path}")
|
|
102
|
+
break
|
|
103
|
+
if model_path:
|
|
104
|
+
break
|
|
105
|
+
|
|
106
|
+
if model_path and os.path.exists(model_path):
|
|
107
|
+
try:
|
|
108
|
+
# Initialize with minimal settings
|
|
109
|
+
self.local_llm = Llama(
|
|
110
|
+
model_path=model_path,
|
|
111
|
+
n_ctx=2048, # Context window size
|
|
112
|
+
n_threads=4 # Number of CPU threads to use
|
|
113
|
+
)
|
|
114
|
+
logging.info(f"Local LLM initialized with model: {model_path}")
|
|
115
|
+
except Exception as e:
|
|
116
|
+
logging.error(f"Error initializing local LLM: {str(e)}")
|
|
117
|
+
else:
|
|
118
|
+
logging.warning("No local model found. Set LLAMA_MODEL_PATH environment variable to use local LLM.")
|
|
119
|
+
else:
|
|
120
|
+
logging.warning("llama-cpp-python package not installed. Local LLM features will not work.")
|
|
121
|
+
|
|
122
|
+
def set_model(self, model_name: str) -> bool:
|
|
123
|
+
"""
|
|
124
|
+
Set the AI model provider to use
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
model_name: Model provider name ("openai", "claude", "local")
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
True if successful, False otherwise
|
|
131
|
+
"""
|
|
132
|
+
model_name = model_name.lower()
|
|
133
|
+
|
|
134
|
+
if model_name == "openai" and not self.openai_client:
|
|
135
|
+
logging.error("OpenAI client not available. Check API key and package installation.")
|
|
136
|
+
return False
|
|
137
|
+
elif model_name == "claude" and not self.anthropic_client:
|
|
138
|
+
logging.error("Claude client not available. Check API key and package installation.")
|
|
139
|
+
return False
|
|
140
|
+
elif model_name == "local" and not self.local_llm:
|
|
141
|
+
if not LLAMA_AVAILABLE:
|
|
142
|
+
logging.error("Local LLM not available. Please install llama-cpp-python package.")
|
|
143
|
+
else:
|
|
144
|
+
logging.error("Local LLM not initialized. Set LLAMA_MODEL_PATH environment variable.")
|
|
145
|
+
return False
|
|
146
|
+
elif model_name not in ["openai", "claude", "local"]:
|
|
147
|
+
logging.error(f"Unknown model: {model_name}")
|
|
148
|
+
return False
|
|
149
|
+
|
|
150
|
+
self.current_model = model_name
|
|
151
|
+
logging.info(f"AI model set to: {model_name}")
|
|
152
|
+
return True
|
|
153
|
+
|
|
154
|
+
def _create_completion(self, system_prompt: str, user_prompt: str) -> Optional[str]:
|
|
155
|
+
"""
|
|
156
|
+
Create an AI completion using the selected model provider
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
system_prompt: System instructions
|
|
160
|
+
user_prompt: User query
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
Generated text or None if the request failed
|
|
164
|
+
"""
|
|
165
|
+
# Check which model is currently selected
|
|
166
|
+
if self.current_model == "openai":
|
|
167
|
+
return self._openai_completion(system_prompt, user_prompt)
|
|
168
|
+
elif self.current_model == "claude":
|
|
169
|
+
return self._anthropic_completion(system_prompt, user_prompt)
|
|
170
|
+
elif self.current_model == "local":
|
|
171
|
+
return self._local_completion(system_prompt, user_prompt)
|
|
172
|
+
else:
|
|
173
|
+
return f"Unknown model type: {self.current_model}"
|
|
174
|
+
|
|
175
|
+
def _openai_completion(self, system_prompt: str, user_prompt: str) -> Optional[str]:
|
|
176
|
+
"""Create a completion using OpenAI"""
|
|
177
|
+
if not OPENAI_AVAILABLE:
|
|
178
|
+
return "OpenAI package not installed. Please install it with 'pip install openai'."
|
|
179
|
+
|
|
180
|
+
if not self.openai_client:
|
|
181
|
+
return "OpenAI API unavailable. Please set OPENAI_API_KEY environment variable."
|
|
182
|
+
|
|
183
|
+
try:
|
|
184
|
+
# Use the gpt-4o model released after the knowledge cutoff
|
|
185
|
+
# the newest OpenAI model is "gpt-4o" which was released May 13, 2024.
|
|
186
|
+
# do not change this unless explicitly requested by the user
|
|
187
|
+
response = self.openai_client.chat.completions.create(
|
|
188
|
+
model="gpt-4o",
|
|
189
|
+
messages=[
|
|
190
|
+
{"role": "system", "content": system_prompt},
|
|
191
|
+
{"role": "user", "content": user_prompt}
|
|
192
|
+
],
|
|
193
|
+
temperature=0.2,
|
|
194
|
+
max_tokens=1000
|
|
195
|
+
)
|
|
196
|
+
return response.choices[0].message.content
|
|
197
|
+
except Exception as e:
|
|
198
|
+
logging.error(f"OpenAI API error: {str(e)}")
|
|
199
|
+
return f"Error: {str(e)}"
|
|
200
|
+
|
|
201
|
+
def _anthropic_completion(self, system_prompt: str, user_prompt: str) -> Optional[str]:
|
|
202
|
+
"""Create a completion using Anthropic Claude"""
|
|
203
|
+
if not self.anthropic_client:
|
|
204
|
+
return "Anthropic Claude API unavailable. Please set ANTHROPIC_API_KEY environment variable."
|
|
205
|
+
|
|
206
|
+
try:
|
|
207
|
+
# Use the claude-3-5-sonnet-20241022 model, the latest available
|
|
208
|
+
response = self.anthropic_client.messages.create(
|
|
209
|
+
model="claude-3-5-sonnet-20241022",
|
|
210
|
+
system=system_prompt,
|
|
211
|
+
messages=[
|
|
212
|
+
{"role": "user", "content": user_prompt}
|
|
213
|
+
],
|
|
214
|
+
temperature=0.2,
|
|
215
|
+
max_tokens=1000
|
|
216
|
+
)
|
|
217
|
+
return response.content[0].text
|
|
218
|
+
except Exception as e:
|
|
219
|
+
logging.error(f"Anthropic API error: {str(e)}")
|
|
220
|
+
return f"Error: {str(e)}"
|
|
221
|
+
|
|
222
|
+
def _local_completion(self, system_prompt: str, user_prompt: str) -> Optional[str]:
|
|
223
|
+
"""Create a completion using a local model with llama.cpp"""
|
|
224
|
+
if not LLAMA_AVAILABLE:
|
|
225
|
+
return "llama-cpp-python package not installed. Please install it with 'pip install llama-cpp-python'."
|
|
226
|
+
|
|
227
|
+
if not self.local_llm:
|
|
228
|
+
return ("Local LLM not initialized. Please set LLAMA_MODEL_PATH environment variable "
|
|
229
|
+
"or place a supported model in ./models directory.")
|
|
230
|
+
|
|
231
|
+
try:
|
|
232
|
+
# Format the prompt in a chat-like format that local models can understand
|
|
233
|
+
formatted_prompt = f"""
|
|
234
|
+
<|system|>
|
|
235
|
+
{system_prompt}
|
|
236
|
+
<|user|>
|
|
237
|
+
{user_prompt}
|
|
238
|
+
<|assistant|>
|
|
239
|
+
"""
|
|
240
|
+
# Generate completion with the local model
|
|
241
|
+
start_time = time.time()
|
|
242
|
+
logging.info("Starting local LLM inference...")
|
|
243
|
+
|
|
244
|
+
# Use the llama.cpp API to generate text
|
|
245
|
+
output = self.local_llm(
|
|
246
|
+
formatted_prompt,
|
|
247
|
+
max_tokens=1000,
|
|
248
|
+
stop=["<|user|>", "<|system|>"], # Stop tokens
|
|
249
|
+
echo=False, # Don't echo the prompt
|
|
250
|
+
temperature=0.2, # Lower temp for more deterministic outputs
|
|
251
|
+
top_p=0.95, # Nucleus sampling for more focused outputs
|
|
252
|
+
repeat_penalty=1.1 # Slight penalty for repetition
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
# Extract the generated text from the model output
|
|
256
|
+
response = output["choices"][0]["text"].strip()
|
|
257
|
+
|
|
258
|
+
elapsed_time = time.time() - start_time
|
|
259
|
+
logging.info(f"Local LLM inference completed in {elapsed_time:.2f} seconds.")
|
|
260
|
+
|
|
261
|
+
return response
|
|
262
|
+
except Exception as e:
|
|
263
|
+
logging.error(f"Local LLM error: {str(e)}")
|
|
264
|
+
return f"Error using local LLM: {str(e)}"
|
|
265
|
+
|
|
266
|
+
def get_explanation(self, code: str, context: str) -> str:
|
|
267
|
+
"""
|
|
268
|
+
Get an explanation of the provided code
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
code: The specific code to explain
|
|
272
|
+
context: The surrounding code for context
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
A detailed explanation of the code
|
|
276
|
+
"""
|
|
277
|
+
system_prompt = (
|
|
278
|
+
"You are an expert code analyst. "
|
|
279
|
+
"Provide a detailed explanation of the provided code, "
|
|
280
|
+
"including its purpose, how it works, and any potential issues. "
|
|
281
|
+
"Focus on clarity and depth of explanation."
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
user_prompt = f"""
|
|
285
|
+
# Code to explain:
|
|
286
|
+
```
|
|
287
|
+
{code}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
# Context (surrounding code):
|
|
291
|
+
```
|
|
292
|
+
{context}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Please explain this code in detail.
|
|
296
|
+
"""
|
|
297
|
+
|
|
298
|
+
explanation = self._create_completion(system_prompt, user_prompt)
|
|
299
|
+
return explanation or "Failed to generate explanation."
|
|
300
|
+
|
|
301
|
+
def get_improvement(self, code: str, context: str) -> str:
|
|
302
|
+
"""
|
|
303
|
+
Get an improved version of the provided code with structured output
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
code: The specific code to improve
|
|
307
|
+
context: The surrounding code for context
|
|
308
|
+
|
|
309
|
+
Returns:
|
|
310
|
+
A structured string with EXPLANATION and IMPROVED_CODE sections
|
|
311
|
+
"""
|
|
312
|
+
system_prompt = (
|
|
313
|
+
"You are an expert code improver. "
|
|
314
|
+
"Analyze the provided code and suggest improvements. "
|
|
315
|
+
"Maintain the original functionality while making enhancements for: "
|
|
316
|
+
"performance, readability, maintainability, or error handling. "
|
|
317
|
+
"YOUR RESPONSE MUST USE THIS EXACT FORMAT with these section headers:\n\n"
|
|
318
|
+
"# EXPLANATION\n<Your detailed explanation of all improvements>\n\n"
|
|
319
|
+
"# IMPROVED_CODE\n<The complete improved code without any markdown formatting>\n\n"
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
user_prompt = f"""
|
|
323
|
+
# Code to improve:
|
|
324
|
+
```
|
|
325
|
+
{code}
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
# Context (surrounding code):
|
|
329
|
+
```
|
|
330
|
+
{context}
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
Please provide your response using the EXACT format with these section headers:
|
|
334
|
+
1. Start with "# EXPLANATION" followed by your detailed explanation
|
|
335
|
+
2. Then include "# IMPROVED_CODE" followed by just the improved code (no markdown code blocks)
|
|
336
|
+
"""
|
|
337
|
+
|
|
338
|
+
improvement = self._create_completion(system_prompt, user_prompt)
|
|
339
|
+
if not improvement:
|
|
340
|
+
return "Failed to generate improvement."
|
|
341
|
+
|
|
342
|
+
# Ensure we have the two required sections
|
|
343
|
+
if "# EXPLANATION" not in improvement or "# IMPROVED_CODE" not in improvement:
|
|
344
|
+
# Try to parse it anyway by adding the headers
|
|
345
|
+
processed = "# EXPLANATION\n" + improvement + "\n\n# IMPROVED_CODE\n" + code
|
|
346
|
+
return processed
|
|
347
|
+
|
|
348
|
+
return improvement
|
|
349
|
+
|
|
350
|
+
def generate_code(self, specification: str, context: str) -> str:
|
|
351
|
+
"""
|
|
352
|
+
Generate code based on a specification
|
|
353
|
+
|
|
354
|
+
Args:
|
|
355
|
+
specification: The code or comments describing what to generate
|
|
356
|
+
context: The surrounding code for context
|
|
357
|
+
|
|
358
|
+
Returns:
|
|
359
|
+
Generated code based on the specification
|
|
360
|
+
"""
|
|
361
|
+
system_prompt = (
|
|
362
|
+
"You are an expert code generator. "
|
|
363
|
+
"Generate high-quality, efficient code based on the specification. "
|
|
364
|
+
"Ensure the generated code fits well with the provided context. "
|
|
365
|
+
"Focus on correctness, efficiency, and readability. "
|
|
366
|
+
"Include helpful comments where appropriate."
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
user_prompt = f"""
|
|
370
|
+
# Specification:
|
|
371
|
+
{specification}
|
|
372
|
+
|
|
373
|
+
# Context (surrounding code):
|
|
374
|
+
```
|
|
375
|
+
{context}
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
Please generate code that meets this specification and fits well with the context.
|
|
379
|
+
"""
|
|
380
|
+
|
|
381
|
+
generated_code = self._create_completion(system_prompt, user_prompt)
|
|
382
|
+
return generated_code or "Failed to generate code."
|
|
383
|
+
|
|
384
|
+
def custom_query(self, query: str, context: str) -> str:
|
|
385
|
+
"""
|
|
386
|
+
Process a custom query about the code
|
|
387
|
+
|
|
388
|
+
Args:
|
|
389
|
+
query: The user's query
|
|
390
|
+
context: The code context for reference
|
|
391
|
+
|
|
392
|
+
Returns:
|
|
393
|
+
The AI's response to the query
|
|
394
|
+
"""
|
|
395
|
+
system_prompt = (
|
|
396
|
+
"You are an expert programming assistant. "
|
|
397
|
+
"Answer the user's query about their code accurately and helpfully. "
|
|
398
|
+
"If the query is unclear, ask for clarification. "
|
|
399
|
+
"Provide factual, specific information without making assumptions. "
|
|
400
|
+
"When relevant, include code examples."
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
user_prompt = f"""
|
|
404
|
+
# Query:
|
|
405
|
+
{query}
|
|
406
|
+
|
|
407
|
+
# Code context:
|
|
408
|
+
```
|
|
409
|
+
{context}
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
Please respond to this query considering the code context.
|
|
413
|
+
"""
|
|
414
|
+
|
|
415
|
+
response = self._create_completion(system_prompt, user_prompt)
|
|
416
|
+
return response or "Failed to process query."
|
|
417
|
+
|
|
418
|
+
def analyze_code(self, code: str, context: str) -> str:
|
|
419
|
+
"""
|
|
420
|
+
Analyze code complexity and identify potential bugs
|
|
421
|
+
|
|
422
|
+
Args:
|
|
423
|
+
code: The specific code to analyze
|
|
424
|
+
context: The surrounding code for context
|
|
425
|
+
|
|
426
|
+
Returns:
|
|
427
|
+
A detailed analysis of code complexity and potential bugs
|
|
428
|
+
"""
|
|
429
|
+
system_prompt = (
|
|
430
|
+
"You are an expert code analyzer specializing in identifying complexity issues and potential bugs. "
|
|
431
|
+
"Analyze the provided code thoroughly and provide detailed feedback on: "
|
|
432
|
+
"1. Cyclomatic complexity - identify complex functions or methods and suggest simplification "
|
|
433
|
+
"2. Potential bugs - edge cases, error handling gaps, race conditions, etc. "
|
|
434
|
+
"3. Code smells - duplicate code, long methods, long parameter lists "
|
|
435
|
+
"4. Performance issues - inefficient algorithms, memory usage concerns "
|
|
436
|
+
"5. Security vulnerabilities - if any are evident "
|
|
437
|
+
"6. Maintainability concerns "
|
|
438
|
+
"Format your response with clear sections for each category and provide line references. "
|
|
439
|
+
"For each issue, explain why it's problematic and suggest a practical solution."
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
user_prompt = f"""
|
|
443
|
+
# Code to analyze:
|
|
444
|
+
```
|
|
445
|
+
{code}
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
# Context (surrounding code):
|
|
449
|
+
```
|
|
450
|
+
{context}
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
Please provide a comprehensive analysis of this code, focusing on complexity and potential bugs.
|
|
454
|
+
"""
|
|
455
|
+
|
|
456
|
+
analysis = self._create_completion(system_prompt, user_prompt)
|
|
457
|
+
return analysis or "Failed to analyze code."
|
aivim/buffer.py
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Buffer implementation for AIVim
|
|
3
|
+
"""
|
|
4
|
+
from typing import List, Optional, Tuple
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Buffer:
|
|
8
|
+
"""
|
|
9
|
+
Represents an editing buffer that holds text content
|
|
10
|
+
"""
|
|
11
|
+
def __init__(self):
|
|
12
|
+
"""Initialize an empty buffer"""
|
|
13
|
+
self.lines = [""]
|
|
14
|
+
self.modified = False
|
|
15
|
+
|
|
16
|
+
# Selection state
|
|
17
|
+
self.selection_start = None # (y, x)
|
|
18
|
+
self.selection_end = None # (y, x)
|
|
19
|
+
|
|
20
|
+
def get_lines(self) -> List[str]:
|
|
21
|
+
"""Get all lines in the buffer"""
|
|
22
|
+
return self.lines
|
|
23
|
+
|
|
24
|
+
def get_line(self, index: int) -> str:
|
|
25
|
+
"""Get a specific line by index"""
|
|
26
|
+
if 0 <= index < len(self.lines):
|
|
27
|
+
return self.lines[index]
|
|
28
|
+
return ""
|
|
29
|
+
|
|
30
|
+
def set_line(self, index: int, content: str) -> None:
|
|
31
|
+
"""Set the content of a specific line"""
|
|
32
|
+
if 0 <= index < len(self.lines):
|
|
33
|
+
if self.lines[index] != content:
|
|
34
|
+
self.lines[index] = content
|
|
35
|
+
self.modified = True
|
|
36
|
+
|
|
37
|
+
def insert_line(self, index: int, content: str) -> None:
|
|
38
|
+
"""Insert a new line at the specified index"""
|
|
39
|
+
if 0 <= index <= len(self.lines):
|
|
40
|
+
self.lines.insert(index, content)
|
|
41
|
+
self.modified = True
|
|
42
|
+
|
|
43
|
+
def delete_line(self, index: int) -> None:
|
|
44
|
+
"""Delete the line at the specified index"""
|
|
45
|
+
if 0 <= index < len(self.lines):
|
|
46
|
+
del self.lines[index]
|
|
47
|
+
# Ensure buffer always has at least one line
|
|
48
|
+
if not self.lines:
|
|
49
|
+
self.lines = [""]
|
|
50
|
+
self.modified = True
|
|
51
|
+
|
|
52
|
+
def get_content(self) -> str:
|
|
53
|
+
"""Get the entire buffer content as a string"""
|
|
54
|
+
return "\n".join(self.lines)
|
|
55
|
+
|
|
56
|
+
def set_content(self, content: str) -> None:
|
|
57
|
+
"""Set the entire buffer content"""
|
|
58
|
+
if content:
|
|
59
|
+
self.lines = content.split("\n")
|
|
60
|
+
else:
|
|
61
|
+
self.lines = [""]
|
|
62
|
+
self.modified = True
|
|
63
|
+
|
|
64
|
+
def set_lines(self, lines: List[str]) -> None:
|
|
65
|
+
"""Set all lines in the buffer"""
|
|
66
|
+
if lines:
|
|
67
|
+
self.lines = lines.copy()
|
|
68
|
+
else:
|
|
69
|
+
self.lines = [""]
|
|
70
|
+
self.modified = True
|
|
71
|
+
|
|
72
|
+
def is_modified(self) -> bool:
|
|
73
|
+
"""Check if the buffer has been modified"""
|
|
74
|
+
return self.modified
|
|
75
|
+
|
|
76
|
+
def mark_as_saved(self) -> None:
|
|
77
|
+
"""Mark the buffer as saved (not modified)"""
|
|
78
|
+
self.modified = False
|
|
79
|
+
|
|
80
|
+
def start_selection(self, y: int, x: int) -> None:
|
|
81
|
+
"""Start a selection at the specified position"""
|
|
82
|
+
self.selection_start = (y, x)
|
|
83
|
+
self.selection_end = (y, x)
|
|
84
|
+
|
|
85
|
+
def update_selection(self, y: int, x: int) -> None:
|
|
86
|
+
"""Update the end point of the current selection"""
|
|
87
|
+
self.selection_end = (y, x)
|
|
88
|
+
|
|
89
|
+
def end_selection(self) -> None:
|
|
90
|
+
"""End the current selection"""
|
|
91
|
+
self.selection_start = None
|
|
92
|
+
self.selection_end = None
|
|
93
|
+
|
|
94
|
+
def get_selection(self) -> Tuple[Optional[Tuple[int, int]], Optional[Tuple[int, int]]]:
|
|
95
|
+
"""Get the current selection start and end points"""
|
|
96
|
+
return (self.selection_start, self.selection_end)
|
|
97
|
+
|
|
98
|
+
def get_selection_text(self) -> str:
|
|
99
|
+
"""Get the text in the current selection"""
|
|
100
|
+
if not self.selection_start or not self.selection_end:
|
|
101
|
+
return ""
|
|
102
|
+
|
|
103
|
+
start_y, start_x = self.selection_start
|
|
104
|
+
end_y, end_x = self.selection_end
|
|
105
|
+
|
|
106
|
+
# Ensure start is before end
|
|
107
|
+
if (start_y > end_y) or (start_y == end_y and start_x > end_x):
|
|
108
|
+
start_y, start_x, end_y, end_x = end_y, end_x, start_y, start_x
|
|
109
|
+
|
|
110
|
+
if start_y == end_y:
|
|
111
|
+
# Single line selection
|
|
112
|
+
return self.lines[start_y][start_x:end_x]
|
|
113
|
+
|
|
114
|
+
# Multi-line selection
|
|
115
|
+
result = [self.lines[start_y][start_x:]]
|
|
116
|
+
for i in range(start_y + 1, end_y):
|
|
117
|
+
result.append(self.lines[i])
|
|
118
|
+
result.append(self.lines[end_y][:end_x])
|
|
119
|
+
|
|
120
|
+
return "\n".join(result)
|