Skip to content

Commit 0677c71

Browse files
committed
Implement code prefix for checking and completion
- Bump to 0.0.29
1 parent 0a787ff commit 0677c71

File tree

11 files changed

+168
-63
lines changed

11 files changed

+168
-63
lines changed

examples/example_single_editor.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
import sys
22
import os
3-
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
3+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
44
from pyqt_code_editor import watchdog
55
from qtpy.QtWidgets import QApplication, QVBoxLayout, QWidget
66
from pyqt_code_editor.code_editors import create_editor
7+
from pyqt_code_editor.environment_manager import environment_manager
78

89

910
class MainWindow(QWidget):
1011
def __init__(self, path=None):
1112
super().__init__()
1213
self.setWindowTitle("PyQtCodeEditor")
1314
layout = QVBoxLayout()
15+
environment_manager.prefix = 'import math'
1416
self.editor = create_editor(path, parent=self)
1517
layout.addWidget(self.editor)
1618
self.setLayout(layout)

pyqt_code_editor/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22

33
from ._settings import settings
44

5-
__version__ = '0.0.28'
5+
__version__ = '0.0.29'

pyqt_code_editor/environment_manager.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,30 +7,30 @@ class EnvironmentManager(QObject):
77
"""Singleton class to share the current environment and language between all
88
components of the app.
99
"""
10-
environment_changed = Signal(str, str, str)
10+
environment_changed = Signal(str, str, str, str)
1111

1212
def __init__(self):
1313
super().__init__()
1414
self.name = None
1515
self.path = None
1616
self.language = None
17+
self.prefix = None
1718

1819
@property
1920
def current_environment(self):
2021
return self.name, self.path, self.language
2122

22-
def set_environment(self, name, path, language):
23-
path = os.path.abspath(path)
24-
if path == self.path:
25-
return
26-
if not os.path.exists(path):
27-
logger.warning(f'no such environment: {path}')
28-
return
29-
logger.info(f'environment changed: {path}')
30-
self.name = name
31-
self.path = path
32-
self.language = language
33-
self.environment_changed.emit(name, path, language)
23+
def set_environment(self, name, path, language, prefix=None):
24+
if path != self.path:
25+
# The path may not exist if the environment is simply python or
26+
# python3, in which case we do not need to specify it at all.
27+
if not os.path.exists(path):
28+
path = None
29+
self.name = name
30+
self.path = path
31+
self.language = language
32+
self.prefix = prefix
33+
self.environment_changed.emit(name, path, language, prefix)
3434

3535

3636
# Singleton instance

pyqt_code_editor/mixins/check.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
from qtpy.QtWidgets import QWidget, QPlainTextEdit
2-
from qtpy.QtCore import Qt, QRect, QPoint, QSize, QTimer
3-
from qtpy.QtGui import QPainter, QColor, QBrush
41
import logging
2+
from qtpy.QtCore import QTimer
3+
from qtpy.QtGui import QColor
4+
from ..environment_manager import environment_manager
55
logger = logging.getLogger(__name__)
66

77

@@ -50,8 +50,8 @@ def _execute_check(self):
5050
self.send_worker_request(
5151
action='check',
5252
code=code,
53-
language=getattr(self, "code_editor_language", "python")
54-
)
53+
language=getattr(self, "code_editor_language", "python"),
54+
prefix=environment_manager.prefix)
5555

5656
def handle_worker_result(self, action: str, result):
5757
"""

pyqt_code_editor/mixins/complete.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,8 @@ def _cm_request_completion(self, multiline=False, full=False):
276276
multiline=multiline,
277277
path=self.code_editor_file_path,
278278
language=self.code_editor_language,
279-
env_path=environment_manager.path)
279+
env_path=environment_manager.path,
280+
prefix=environment_manager.prefix)
280281

281282
def _cm_request_calltip(self):
282283
"""Send a calltip request."""
@@ -288,7 +289,8 @@ def _cm_request_calltip(self):
288289
cursor_pos=cursor_pos,
289290
path=self.code_editor_file_path,
290291
language=self.code_editor_language,
291-
env_path=environment_manager.path)
292+
env_path=environment_manager.path,
293+
prefix=environment_manager.prefix)
292294

293295
def _cm_hide_calltip(self):
294296
"""Hide the calltip widget."""

pyqt_code_editor/worker/languages/generic.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
from ..providers import symbol, codestral
22

3-
def complete(code, cursor_pos, path, multiline, full, env_path):
3+
def complete(code, cursor_pos, path, multiline, full, env_path, prefix):
44
if full or multiline:
55
completions = codestral.codestral_complete(
6-
code, cursor_pos, multiline=multiline)
6+
code, cursor_pos, multiline=multiline, prefix=prefix)
77
else:
88
completions = []
99
if not multiline:

pyqt_code_editor/worker/languages/python.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
from ..providers import jedi, codestral, ruff
22

33

4-
def complete(code, cursor_pos, path, multiline, full, env_path):
4+
def complete(code, cursor_pos, path, multiline, full, env_path, prefix):
55
if full or multiline:
66
codestral_completions = codestral.codestral_complete(
7-
code, cursor_pos, multiline=multiline)
7+
code, cursor_pos, multiline=multiline, prefix=prefix)
88
else:
99
codestral_completions = []
1010
if not multiline:
1111
jedi_completions = jedi.jedi_complete(code, cursor_pos, path=path,
12-
env_path=env_path)
12+
env_path=env_path,
13+
prefix=prefix)
1314
else:
1415
jedi_completions = []
1516
# If there is at least one jedi completion, it is always insert at first.

pyqt_code_editor/worker/process.py

Lines changed: 109 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,98 @@
88

99
def main_worker_process_function(request_queue, result_queue):
1010
"""
11-
Runs in a separate process, handling requests in dict form.
12-
Supported actions include:
13-
- 'complete': triggers code completion
14-
- 'calltip': fetches calltip/signature info
15-
- 'check': fetches code check/ linting info
16-
- 'setting': updates settings in the 'settings' module
17-
- 'quit': shuts down the worker
11+
Main worker process for handling editor backend requests.
12+
13+
Runs in a separate process, continuously processing requests from a queue
14+
and sending results back through another queue. Supports code completion,
15+
calltips, symbol extraction, code checking, and settings management.
16+
17+
Parameters
18+
----------
19+
request_queue : multiprocessing.Queue
20+
Queue containing request dictionaries to process
21+
result_queue : multiprocessing.Queue
22+
Queue where results are sent back to the main process
23+
24+
Request Format
25+
--------------
26+
Each request must be a dictionary with the following structure:
27+
28+
Required fields:
29+
action : str
30+
The action to perform. One of: 'complete', 'calltip', 'symbols',
31+
'check', 'set_settings', 'quit'
32+
33+
Optional fields (depending on action):
34+
language : str, default 'text'
35+
Programming language for syntax-aware processing
36+
code : str, default ''
37+
Source code content to analyze
38+
cursor_pos : int, default 0
39+
Character position of cursor in the code
40+
path : str, optional
41+
File path for context-aware processing
42+
multiline : bool, default False
43+
Whether to enable multiline completion mode
44+
full : bool, default False
45+
Whether to return full completion details
46+
env_path : str, optional
47+
Path to Python environment for context
48+
prefix : str, optional
49+
Prefix string for filtering results
50+
settings : dict, optional
51+
Settings to update (only for 'set_settings' action)
52+
53+
Response Format
54+
---------------
55+
Results are dictionaries placed on result_queue with action-specific content:
56+
57+
For 'complete' action:
58+
{
59+
'action': 'complete',
60+
'completions': list or None,
61+
'cursor_pos': int,
62+
'multiline': bool,
63+
'full': bool
64+
}
65+
66+
For 'calltip' action:
67+
{
68+
'action': 'calltip',
69+
'signatures': list or None,
70+
'cursor_pos': int
71+
}
72+
73+
For 'symbols' action:
74+
{
75+
'action': 'symbols',
76+
'symbols': list
77+
}
78+
79+
For 'check' action:
80+
{
81+
'action': 'check',
82+
'messages': dict
83+
}
84+
85+
Behavior
86+
--------
87+
- Runs indefinitely until receiving a 'quit' action
88+
- Dynamically loads language-specific worker modules on first use
89+
- Caches imported worker modules for efficiency
90+
- Falls back to generic worker functions if language-specific module unavailable
91+
- Skips invalid requests (None, non-dict, or missing 'action' field)
92+
- Logs all major operations and errors
93+
- For 'set_settings' action, updates the settings module attributes directly
94+
- Worker modules must provide functions: complete, calltip, symbols, check
95+
(functions can be None if not supported for that language)
96+
97+
Notes
98+
-----
99+
This function is designed to run in a separate process to avoid blocking
100+
the main editor thread during potentially slow operations like code analysis.
101+
Language-specific worker modules are expected to be in the 'languages'
102+
subpackage.
18103
"""
19104
logger.info("Started completion worker.")
20105
while True:
@@ -25,13 +110,21 @@ def main_worker_process_function(request_queue, result_queue):
25110
if request is None:
26111
logger.info("Received None request (possibly legacy or invalid). Skipping.")
27112
continue
28-
29113
# Expect a dict with at least an 'action' field
30114
if not isinstance(request, dict):
31115
logger.info(f"Invalid request type: {type(request)}. Skipping.")
32-
continue
33-
116+
continue
117+
# Most of the parameters apply to multiple actions, so we extract them
118+
# here.
34119
action = request.get('action', None)
120+
language = request.get('language', 'text')
121+
code = request.get('code', '')
122+
cursor_pos = request.get('cursor_pos', 0)
123+
path = request.get('path', None)
124+
multiline = request.get('multiline', False)
125+
full = request.get('full', False)
126+
env_path = request.get('env_path', None)
127+
prefix = request.get('prefix', None)
35128
if action is None:
36129
logger.info("Request is missing 'action' field. Skipping.")
37130
continue
@@ -46,8 +139,7 @@ def main_worker_process_function(request_queue, result_queue):
46139
break
47140

48141
# Load the worker functions depending on the language. We store the
49-
# imported module in a cache for efficiency
50-
language = request.get('language', 'text')
142+
# imported module in a cache for efficiency
51143
if language not in worker_functions_cache:
52144
try:
53145
worker_functions = importlib.import_module(
@@ -66,16 +158,10 @@ def main_worker_process_function(request_queue, result_queue):
66158
if worker_functions.complete is None:
67159
completions = None
68160
else:
69-
code = request.get('code', '')
70-
cursor_pos = request.get('cursor_pos', 0)
71-
path = request.get('path', None)
72-
multiline = request.get('multiline', False)
73-
full = request.get('full', False)
74-
env_path = request.get('env_path', None)
75161
logger.info(f"Performing code completion: language='{language}', multiline={multiline}, path={path}, env_path={env_path}")
76162
completions = worker_functions.complete(
77163
code, cursor_pos, path=path, multiline=multiline, full=full,
78-
env_path=env_path)
164+
env_path=env_path, prefix=prefix)
79165
if not completions:
80166
logger.info("No completions")
81167
else:
@@ -89,16 +175,13 @@ def main_worker_process_function(request_queue, result_queue):
89175
})
90176

91177
elif action == 'calltip':
92-
cursor_pos = request.get('cursor_pos', 0)
93178
if worker_functions.calltip is None:
94179
signatures = None
95180
else:
96-
code = request.get('code', '')
97-
path = request.get('path', None)
98-
env_path = request.get('env_path', None)
99181
logger.info(f"Performing calltip: language='{language}', path={path}, env_path={env_path}")
100182
signatures = worker_functions.calltip(
101-
code, cursor_pos, path=path, env_path=env_path)
183+
code, cursor_pos, path=path, env_path=env_path,
184+
prefix=prefix)
102185
if signatures is None:
103186
logger.info("No calltip signatures. Sending result back.")
104187
else:
@@ -113,7 +196,6 @@ def main_worker_process_function(request_queue, result_queue):
113196
if worker_functions.symbols is None:
114197
symbols_results = []
115198
else:
116-
code = request.get('code', '')
117199
symbols_results = worker_functions.symbols(code)
118200
result_queue.put({
119201
'action': 'symbols',
@@ -124,11 +206,10 @@ def main_worker_process_function(request_queue, result_queue):
124206
if worker_functions.check is None:
125207
check_results = {}
126208
else:
127-
code = request.get('code', '')
128-
check_results = worker_functions.check(code)
209+
check_results = worker_functions.check(code, prefix=prefix)
129210
result_queue.put({
130211
'action': 'check',
131212
'messages': check_results
132213
})
133214

134-
logger.info("Completion worker has shut down.")
215+
logger.info("Completion worker has shut down.")

pyqt_code_editor/worker/providers/codestral.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66

77

88
def codestral_complete(code: str, cursor_pos: int,
9-
multiline: bool = False) -> list[str]:
9+
multiline: bool = False,
10+
prefix: str | None = None) -> list[str]:
1011
global client
1112

1213
if not settings.codestral_api_key:
@@ -22,6 +23,10 @@ def codestral_complete(code: str, cursor_pos: int,
2223
start = max(0, cursor_pos - settings.codestral_max_context)
2324
end = cursor_pos + settings.codestral_max_context
2425
prompt = code[start: cursor_pos]
26+
if prefix:
27+
if not prefix.endswith('\n'):
28+
prefix += '\n'
29+
prompt = prefix + prompt
2530
suffix = code[cursor_pos: end]
2631

2732
request = dict(

0 commit comments

Comments
 (0)