liveConsole 1.4.0__py3-none-any.whl → 1.4.1__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: liveConsole
3
- Version: 1.4.0
3
+ Version: 1.4.1
4
4
  Summary: An IDLE-like debugger to allow for real-time command injection for debugging and testing python code
5
5
  Author-email: Tzur Soffer <tzur.soffer@gmail.com>
6
6
  License: MIT
@@ -29,6 +29,9 @@ A fully-featured, **live Python console GUI** built with **CustomTkinter** and *
29
29
 
30
30
  * Clickable history of previous commands
31
31
 
32
+ * **Integrated Help Panel** for quick access to Python object documentation
33
+
34
+
32
35
  ## Installation
33
36
 
34
37
  `pip install liveConsole`
@@ -77,6 +80,17 @@ A fully-featured, **live Python console GUI** built with **CustomTkinter** and *
77
80
  * Click to copy them back to the prompt for editing or re-execution.
78
81
 
79
82
 
83
+ ### Help Panel
84
+
85
+ * A resizable right-hand panel that displays Python documentation (help()) for any object.
86
+
87
+ * Opens when clicking ctrl+click on a function/method and can be closed with the "X" button.
88
+
89
+ * Scrollable and syntax-styled.
90
+
91
+ * Perfect for quick reference without leaving the console.
92
+
93
+
80
94
  ### Easy Integration
81
95
 
82
96
  * Automatically grabs **caller frame globals and locals** if not provided.
@@ -0,0 +1,12 @@
1
+ __init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ commandHistory.py,sha256=xJtWbJ_vgJo2QGgaZJsApTOi_Hm8Yz0V9_zqQUCItj8,1084
3
+ helpTab.py,sha256=5Fxj_wTNTbuF6sBIN8GdbE6i_zbAEdlrFnbfhnDk5ms,2360
4
+ liveConsole.py,sha256=9Gwevh3q52Qirig9VJ_qnbTuCda2ydyZnUR29kl7Zlw,3839
5
+ mainConsole.py,sha256=MxgJJihgbPtKZ8FqXSFczj5neRVGRiM8RX4lcm0ewk0,12973
6
+ styledTextbox.py,sha256=pc-7gaq_pGTZGZmtr_ARbPuKlKgJYqzD6HTR7tFhx7k,1989
7
+ suggestionManager.py,sha256=GRde3c1gFAWt_3rvBoFt_-Xl0aOzzloBMD7MHJA2W8U,6256
8
+ liveconsole-1.4.1.dist-info/licenses/LICENSE,sha256=7dZ0zL72aGaFE0C9DxacOpnaSkC5jajhG6iL7lqhWmU,1064
9
+ liveconsole-1.4.1.dist-info/METADATA,sha256=fg47oHebYtfDRKCZaFvYOaSHqCTxzTYYWWq87W5_WO0,3648
10
+ liveconsole-1.4.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
11
+ liveconsole-1.4.1.dist-info/top_level.txt,sha256=QkTpA-HOQwPQRHYwLKYJOhu_zwarar6tdVSuPwYPvl8,88
12
+ liveconsole-1.4.1.dist-info/RECORD,,
@@ -0,0 +1,7 @@
1
+ __init__
2
+ commandHistory
3
+ helpTab
4
+ liveConsole
5
+ mainConsole
6
+ styledTextbox
7
+ suggestionManager
mainConsole.py ADDED
@@ -0,0 +1,354 @@
1
+ import threading
2
+ import traceback
3
+ from suggestionManager import CodeSuggestionManager
4
+ from commandHistory import CommandHistory
5
+ from styledTextbox import StyledTextWindow
6
+
7
+ import tkinter as tk
8
+
9
+ class InteractiveConsoleText(StyledTextWindow):
10
+ """TBD"""
11
+
12
+ PROMPT = ">>> "
13
+ PROMPT_LENGTH = 4
14
+
15
+ def __init__(self, master, helpTab, userLocals=None, userGlobals=None, **kwargs):
16
+ super().__init__(master, **kwargs)
17
+
18
+ # Initialize components
19
+ self.suggestionManager = CodeSuggestionManager(self, userLocals=userLocals, userGlobals=userGlobals)
20
+ self.helpTab = helpTab
21
+
22
+ self.navigatingHistory = False
23
+ self.history = CommandHistory()
24
+
25
+ self.inputVar = tk.StringVar()
26
+ self.waitingForInput = False
27
+
28
+ # Track current command
29
+ self.currentCommandLine = 1
30
+ self.isExecuting = False
31
+
32
+ # Setup bindings
33
+ self._setupBindings()
34
+
35
+ # Initialize with first prompt
36
+ self.addPrompt()
37
+
38
+ def _setupBindings(self):
39
+ """Setup all key and mouse bindings."""
40
+ self.bind("<Return>", self.onEnter)
41
+ self.bind("<Shift-Return>", self.onShiftEnter)
42
+ self.bind("<Control-c>", self.cancel)
43
+ self.bind("<Tab>", self.onTab)
44
+ self.bind("<BackSpace>", self.onBackspace)
45
+ self.bind("<KeyRelease>", self.onKeyRelease)
46
+ self.bind("<KeyPress>", self.onKeyPress)
47
+ self.bind("<Button-1>", self.onClick)
48
+ self.bind("<Up>", self.onUp)
49
+ self.bind("<Down>", self.onDown)
50
+
51
+ def getCurrentLineNumber(self):
52
+ """Get the line number where current command starts."""
53
+ return(int(self.index("end-1c").split(".")[0]))
54
+
55
+ def getCommandStartPosition(self):
56
+ """Get the starting position of the current command."""
57
+ return(f"{self.currentCommandLine}.0")
58
+
59
+ def replaceCurrentCommand(self, newCommand):
60
+ """Replace the current command with new text."""
61
+ if self.isExecuting:
62
+ return
63
+
64
+ start = self.getPromptPosition()
65
+ end = "end-1c"
66
+
67
+ self.delete(start, end)
68
+ self.insert(start, newCommand)
69
+ self.see("end")
70
+
71
+ def isCursorInEditableArea(self):
72
+ """Check if cursor is in the editable command area."""
73
+ if self.isExecuting:
74
+ return(False)
75
+
76
+ cursorLine = int(self.index("insert").split(".")[0])
77
+ cursorCol = int(self.index("insert").split(".")[1])
78
+
79
+ return((cursorLine >= self.currentCommandLine and
80
+ (cursorLine > self.currentCommandLine or cursorCol >= self.PROMPT_LENGTH)))
81
+
82
+ def onEnter(self, event):
83
+ """Handle Enter key - execute command."""
84
+ self.suggestionManager.hideSuggestions()
85
+
86
+ if self.waitingForInput:
87
+ line = self.get("insert linestart", "insert lineend").strip()
88
+ self.insert("end", "\n") # move to next line like normal console
89
+ self.inputVar.set(line)
90
+ self.waitingForInput = False
91
+ return("break")
92
+
93
+ if self.isExecuting:
94
+ return("break")
95
+
96
+ command = self.getCurrentCommand()
97
+
98
+ if not command.strip():
99
+ return("break")
100
+
101
+ # Check if statement is incomplete
102
+ if self.isIncompleteStatement(command):
103
+ return(self.onShiftEnter(event))
104
+
105
+ # Execute the command
106
+ self.history.add(command)
107
+ self.mark_set("insert", "end")
108
+ self.insert("end", "\n")
109
+ self.see("end")
110
+
111
+ # Execute in thread
112
+ self.isExecuting = True
113
+ threading.Thread(
114
+ target=self.executeCommandThreaded,
115
+ args=(command,),
116
+ daemon=True
117
+ ).start()
118
+
119
+ return("break")
120
+
121
+ def readInput(self):
122
+ """Return the last entered line when input() is called."""
123
+ self.waitingForInput = True
124
+ self.wait_variable(self.inputVar) #< waits until Enter is pressed
125
+ line = self.inputVar.get()
126
+ self.inputVar.set("") #< reset
127
+ return(line)
128
+
129
+ def onShiftEnter(self, event):
130
+ """Handle Shift+Enter - new line with auto-indent."""
131
+ self.suggestionManager.hideSuggestions()
132
+
133
+ if self.isExecuting:
134
+ return("break")
135
+
136
+ # Get current line for indent calculation
137
+ cursorPos = self.index("insert")
138
+ lineStart = self.index(f"{cursorPos} linestart")
139
+ lineEnd = self.index(f"{cursorPos} lineend")
140
+ currentLine = self.get(lineStart, lineEnd)
141
+
142
+ # Calculate indentation
143
+ indent = self.calculateIndent(currentLine)
144
+
145
+ # Insert newline with indent
146
+ self.insert("insert", "\n" + " " * indent)
147
+ self.see("end")
148
+
149
+ return("break")
150
+
151
+ def onTab(self, event):
152
+ """Handle Tab key for autocompletion."""
153
+ if self.isExecuting:
154
+ return("break")
155
+
156
+ if self.suggestionManager.suggestionWindow and \
157
+ self.suggestionManager.suggestionWindow.winfo_viewable():
158
+ self.suggestionManager.applySuggestion()
159
+ else:
160
+ self.suggestionManager.showSuggestions()
161
+
162
+ return("break")
163
+
164
+ def onBackspace(self, event):
165
+ """Prevent backspace from deleting the prompt."""
166
+ if not self.isCursorInEditableArea():
167
+ return("break")
168
+
169
+ # Check if we're at the prompt boundary
170
+ cursorPos = self.index("insert")
171
+ promptPos = self.getPromptPosition()
172
+
173
+ if self.compare(cursorPos, "<=", promptPos):
174
+ return("break")
175
+
176
+ def onClick(self, event):
177
+ """Handle mouse clicks - Ctrl+Click opens help for the clicked word."""
178
+ self.suggestionManager.hideSuggestions()
179
+
180
+ if event.state & 0x4: #< Ctrl pressed
181
+ clickIndex = self.index(f"@{event.x},{event.y}") # mouse index
182
+
183
+ # Get the full line
184
+ i = int(clickIndex.split('.')[1])
185
+ lineNum = clickIndex.split('.')[0]
186
+ lineStart = f"{lineNum}.0"
187
+ lineEnd = f"{lineNum}.end"
188
+ lineText = self.get(lineStart, lineEnd)
189
+
190
+ wordEndIndex = self.index(f"{clickIndex} wordend")
191
+ # obj = self.get(f"{clickIndex} wordstart", f"{clickIndex} wordend").strip() #< Get the word at that index
192
+ obj = ""
193
+ for i in range (i-1,2, -1):
194
+ letter = lineText[i]
195
+ if (not (letter.isalnum() or letter == "_" or letter == ".")): #< or (letter in " \n\t\r")
196
+ obj = lineText[i+1: int(wordEndIndex.split('.')[1])]
197
+ break
198
+
199
+
200
+ if obj:
201
+ self.helpTab.updateHelp(obj)
202
+ self.helpTab.open()
203
+
204
+ return("break") #< Prevent default cursor behavior
205
+
206
+ return(None) #< Normal click behavior
207
+
208
+ def onKeyPress(self, event):
209
+ """Handle key press events."""
210
+ # print(event.keysym)
211
+ if self.suggestionManager.suggestionWindow and \
212
+ self.suggestionManager.suggestionWindow.winfo_viewable():
213
+ if event.keysym == "Escape":
214
+ self.suggestionManager.hideSuggestions()
215
+ return("break")
216
+
217
+ # Prevent editing outside command area
218
+ if not event.keysym in ["Shift_L", "Shift_R", "Control_L", "Control_R"]:
219
+ self.navigatingHistory = False
220
+ if not self.isCursorInEditableArea():
221
+ self.mark_set("insert", "end")
222
+
223
+ if event.keysym in ["Left", "Right"]:
224
+ if self.index("insert") == self.getPromptPosition():
225
+ self.mark_set("insert", "1.4")
226
+ return("break")
227
+
228
+ def onKeyRelease(self, event):
229
+ """Handle key release events."""
230
+ if event.keysym in ["Return", "Escape", "Left", "Right", "Home", "End"]:
231
+ self.suggestionManager.hideSuggestions()
232
+ elif event.keysym not in ["Up", "Down", "Shift_L", "Shift_R", "Control_L", "Control_R"]:
233
+ if not self.isExecuting:
234
+ self.after_idle(self.suggestionManager.showSuggestions)
235
+ if not self.isExecuting:
236
+ self.after_idle(lambda: self.updateStyling(start=self.getPromptPosition()))
237
+
238
+ def cancel(self, event):
239
+ self.history.add(self.getCurrentCommand())
240
+ self.replaceCurrentCommand("")
241
+
242
+ def historyReplace(self, command):
243
+ if self.getCurrentCommand() == "" or self.navigatingHistory:
244
+ if self.isExecuting:
245
+ return("break")
246
+
247
+ if self.history.index == len(self.history.history):
248
+ self.history.setTemp(self.getCurrentCommand())
249
+
250
+ if command is not None:
251
+ self.replaceCurrentCommand(command)
252
+ self.navigatingHistory = True
253
+ return("break")
254
+
255
+ def onUp(self, event):
256
+ if self.suggestionManager.suggestionWindow and \
257
+ self.suggestionManager.suggestionWindow.winfo_viewable():
258
+ if event.keysym == "Up":
259
+ self.suggestionManager.handleNavigation("up")
260
+ return("break")
261
+ command = self.history.navigateUp()
262
+ return(self.historyReplace(command))
263
+ # self.mark_set("insert", "insert -1 line")
264
+
265
+ def onDown(self, event):
266
+ if self.suggestionManager.suggestionWindow and \
267
+ self.suggestionManager.suggestionWindow.winfo_viewable():
268
+ if event.keysym == "Down":
269
+ self.suggestionManager.handleNavigation("down")
270
+ return("break")
271
+ command = self.history.navigateDown()
272
+ return(self.historyReplace(command))
273
+
274
+ def isIncompleteStatement(self, code):
275
+ """Check if the code is an incomplete statement."""
276
+ lines = code.split("\n")
277
+ if not lines[-1].strip():
278
+ return(False)
279
+
280
+ # Check for line ending with colon
281
+ for line in lines:
282
+ if line.strip().endswith(":"):
283
+ return(True)
284
+
285
+ return(False)
286
+
287
+ def calculateIndent(self, line):
288
+ """Calculate the indentation level for the next line."""
289
+ currentIndent = len(line) - len(line.lstrip())
290
+
291
+ # If line ends with colon, increase indent
292
+ if line.strip().endswith(":"):
293
+ return(currentIndent + 4)
294
+
295
+ return(currentIndent)
296
+
297
+ def writeOutput(self, text, tag="output"):
298
+ """Write output to the console (thread-safe)."""
299
+ def _write():
300
+ self.insert("end", text + "\n", tag)
301
+ self.see("end")
302
+
303
+ self.after(0, _write)
304
+
305
+ def getPromptPosition(self):
306
+ """Get the position right after the prompt on current command line."""
307
+ return(f"{self.currentCommandLine}.{self.PROMPT_LENGTH}")
308
+
309
+ def getCurrentCommand(self):
310
+ """Extract the current command text (without prompt)."""
311
+ start = self.getPromptPosition()
312
+ end = "end-1c"
313
+ return(self.get(start, end))
314
+
315
+ def addPrompt(self):
316
+ """Add a new command prompt."""
317
+ def _add():
318
+ # Store the line number for the new command
319
+ self.currentCommandLine = self.getCurrentLineNumber()
320
+
321
+ # Insert prompt
322
+ self.insert("end", self.PROMPT)
323
+ promptStart = f"{self.currentCommandLine}.0"
324
+ promptEnd = f"{self.currentCommandLine}.{self.PROMPT_LENGTH}"
325
+ self.tag_add("prompt", promptStart, promptEnd)
326
+
327
+ self.mark_set("insert", "end")
328
+ self.see("end")
329
+ self.isExecuting = False
330
+
331
+ if self.isExecuting:
332
+ self.after(0, _add)
333
+ else:
334
+ _add()
335
+
336
+ def executeCommandThreaded(self, command):
337
+ """Execute a command in a separate thread."""
338
+ try:
339
+ # Try eval first for expressions
340
+ result = eval(command, self.master.userGlobals, self.master.userLocals)
341
+ if result is not None:
342
+ self.writeOutput(str(result), "result")
343
+ self.master.userLocals["_"] = result
344
+ except SyntaxError:
345
+ try:
346
+ # Try exec for statements
347
+ exec(command, self.master.userGlobals, self.master.userLocals)
348
+ except Exception:
349
+ self.writeOutput(traceback.format_exc(), "error")
350
+ except Exception:
351
+ self.writeOutput(traceback.format_exc(), "error")
352
+
353
+ # Add new prompt after execution
354
+ self.addPrompt()
styledTextbox.py ADDED
@@ -0,0 +1,51 @@
1
+ import tkinter as tk
2
+ import pygments
3
+ from pygments.lexers import PythonLexer
4
+ from pygments.styles import get_style_by_name
5
+
6
+ class StyledTextWindow(tk.Text):
7
+ def __init__(self, master, **kwargs):
8
+ super().__init__(master, **kwargs)
9
+
10
+ # Syntax highlighting setup
11
+ self.lexer = PythonLexer()
12
+ self.style = get_style_by_name("monokai")
13
+
14
+ # Setup tags
15
+ self._setupTags()
16
+
17
+ def _setupTags(self):
18
+ """Configure text tags for different output types."""
19
+ self.tag_configure("prompt", foreground="#00ff00", font=("Consolas", 12, "bold"))
20
+ self.tag_configure("output", foreground="#ffffff", font=("Consolas", 12))
21
+ self.tag_configure("error", foreground="#ff6666", font=("Consolas", 12))
22
+ self.tag_configure("result", foreground="#66ccff", font=("Consolas", 12))
23
+
24
+ # Configure syntax highlighting tags
25
+ for token, style in self.style:
26
+ if style["color"]:
27
+ fg = f"#{style['color']}"
28
+ font = ("Consolas", 12, "bold" if style["bold"] else "normal")
29
+ self.tag_configure(str(token), foreground=fg, font=font)
30
+
31
+
32
+ def updateStyling(self, start="1.0"):
33
+ """Apply syntax highlighting to the current command."""
34
+ end = "end-1c"
35
+
36
+ for token, _ in self.style:
37
+ self.tag_remove(str(token), start, end)
38
+
39
+ # Get and highlight the command
40
+ command = self.get(start, "end-1c")
41
+ if not command:
42
+ return(-1)
43
+
44
+ self.mark_set("highlight_pos", start)
45
+
46
+ for token, content in pygments.lex(command, self.lexer):
47
+ if content:
48
+ endPos = f"highlight_pos + {len(content)}c"
49
+ if content.strip(): # Only highlight non-whitespace
50
+ self.tag_add(str(token), "highlight_pos", endPos)
51
+ self.mark_set("highlight_pos", endPos)
suggestionManager.py ADDED
@@ -0,0 +1,165 @@
1
+ import keyword
2
+ import builtins
3
+ import tkinter as tk
4
+
5
+ class CodeSuggestionManager:
6
+ """Manages code suggestions and autocomplete functionality."""
7
+
8
+ def __init__(self, textWidget, userLocals, userGlobals):
9
+ self.userLocals = userLocals
10
+ self.userGlobals = userGlobals
11
+ self.textWidget = textWidget
12
+ self.suggestionWindow = None
13
+ self.suggestionListbox = None
14
+ self.suggestions = []
15
+ self.selectedSuggestion = 0
16
+
17
+ # Build suggestion sources
18
+ self.keywords = keyword.kwlist
19
+ self.builtins = [name for name in dir(builtins) if not name.startswith('_')]
20
+
21
+ def getCurrentWord(self):
22
+ """Extract the word being typed at cursor position and suggest dir() if applicable."""
23
+ suggestions = []
24
+ cursorPos = self.textWidget.index(tk.INSERT)
25
+ lineStart = self.textWidget.index(f"{cursorPos} linestart")
26
+ currentLine = self.textWidget.get(lineStart, cursorPos)
27
+
28
+ # Find the current word
29
+ words = currentLine.split()
30
+ if not words:
31
+ return("", suggestions)
32
+
33
+ currentWord = words[-1]
34
+
35
+
36
+ # If the word contains a dot, try to evaluate the base and get its dir()
37
+ if '.' in currentWord:
38
+ try:
39
+ base_expr = '.'.join(currentWord.split('.')[:-1])
40
+ obj = eval(base_expr, self.userLocals, self.userGlobals)
41
+ suggestions = dir(obj)
42
+ except:
43
+ pass
44
+ for char in "([{,.":
45
+ if char in currentWord:
46
+ currentWord = currentWord.split(char)[-1]
47
+
48
+ return(currentWord, suggestions)
49
+
50
+ def getSuggestions(self, partialWord, suggestions=[]):
51
+ """Get code suggestions for partial word."""
52
+ if len(partialWord) < 2:
53
+ return(suggestions)
54
+
55
+ if suggestions != []:
56
+ suggestions = [suggestion for suggestion in suggestions if suggestion.lower().startswith(partialWord.lower())]
57
+ else:
58
+ # Add matching keywords
59
+ for kw in self.keywords:
60
+ if kw.startswith(partialWord.lower()):
61
+ suggestions.append(kw)
62
+
63
+ # Add matching builtins
64
+ for builtin in self.builtins:
65
+ if builtin.startswith(partialWord):
66
+ suggestions.append(builtin)
67
+
68
+ # Add matching variables from namespace
69
+ master = self.textWidget.master
70
+ if hasattr(master, 'userLocals'):
71
+ for var in master.userLocals:
72
+ if var.startswith(partialWord) and not var.startswith('_'):
73
+ suggestions.append(var)
74
+
75
+ if hasattr(master, 'userGlobals'):
76
+ for var in master.userGlobals:
77
+ if var.startswith(partialWord) and not var.startswith('_'):
78
+ suggestions.append(var)
79
+
80
+ # Remove duplicates and sort
81
+ return(sorted(list(set(suggestions))))
82
+
83
+ def showSuggestions(self):
84
+ """Display the suggestions popup."""
85
+ currentWord, extraSuggestions = self.getCurrentWord()
86
+ suggestions = self.getSuggestions(currentWord, extraSuggestions)
87
+
88
+ if not suggestions:
89
+ self.hideSuggestions()
90
+ return
91
+
92
+ self.suggestions = suggestions
93
+ self.selectedSuggestion = 0
94
+
95
+ # Create suggestion window if needed
96
+ if not self.suggestionWindow:
97
+ self._createSuggestionWindow()
98
+
99
+ # Update listbox content
100
+ self.suggestionListbox.delete(0, tk.END)
101
+ for suggestion in suggestions:
102
+ self.suggestionListbox.insert(tk.END, suggestion)
103
+
104
+ self.suggestionListbox.selection_set(0)
105
+
106
+ # Position window near cursor
107
+ self._positionSuggestionWindow()
108
+ self.suggestionWindow.deiconify()
109
+
110
+ def _createSuggestionWindow(self):
111
+ """Create the suggestion popup window."""
112
+ self.suggestionWindow = tk.Toplevel(self.textWidget)
113
+ self.suggestionWindow.wm_overrideredirect(True)
114
+ self.suggestionWindow.configure(bg="#2d2d2d")
115
+
116
+ self.suggestionListbox = tk.Listbox(
117
+ self.suggestionWindow,
118
+ bg="#2d2d2d",
119
+ fg="white",
120
+ selectbackground="#0066cc",
121
+ font=("Consolas", 10),
122
+ height=8
123
+ )
124
+ self.suggestionListbox.pack()
125
+
126
+ def _positionSuggestionWindow(self):
127
+ """Position the suggestion window near the cursor."""
128
+ cursorPos = self.textWidget.index(tk.INSERT)
129
+ x, y, _, _ = self.textWidget.bbox(cursorPos)
130
+ x += self.textWidget.winfo_rootx()
131
+ y += self.textWidget.winfo_rooty() + 20
132
+ self.suggestionWindow.geometry(f"+{x}+{y}")
133
+
134
+ def hideSuggestions(self):
135
+ """Hide the suggestions popup."""
136
+ if self.suggestionWindow:
137
+ self.suggestionWindow.withdraw()
138
+
139
+ def applySuggestion(self, suggestion=None):
140
+ """Apply the selected suggestion at cursor position."""
141
+ if not suggestion and self.suggestions:
142
+ suggestion = self.suggestions[self.selectedSuggestion]
143
+ if not suggestion:
144
+ return
145
+
146
+ currentWord, _ = self.getCurrentWord()
147
+ # Only insert the missing part
148
+ missingPart = suggestion[len(currentWord):]
149
+ cursorPos = self.textWidget.index(tk.INSERT)
150
+ self.textWidget.insert(cursorPos, missingPart)
151
+
152
+ self.hideSuggestions()
153
+
154
+ def handleNavigation(self, direction):
155
+ """Handle up/down navigation in suggestions."""
156
+ if not self.suggestions:
157
+ return
158
+
159
+ if direction == "down":
160
+ self.selectedSuggestion = min(self.selectedSuggestion + 1, len(self.suggestions) - 1)
161
+ else: # up
162
+ self.selectedSuggestion = max(self.selectedSuggestion - 1, 0)
163
+
164
+ self.suggestionListbox.selection_clear(0, tk.END)
165
+ self.suggestionListbox.selection_set(self.selectedSuggestion)
@@ -1,7 +0,0 @@
1
- __init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- liveConsole.py,sha256=2O7tq06LWI8lfb0KaLR_pYEEHG5x591_tiN4hNgHc9I,26891
3
- liveconsole-1.4.0.dist-info/licenses/LICENSE,sha256=7dZ0zL72aGaFE0C9DxacOpnaSkC5jajhG6iL7lqhWmU,1064
4
- liveconsole-1.4.0.dist-info/METADATA,sha256=LgLUoeKAQokjvsQmeUKHGXK_jTSM85xRM73JJ441m70,3252
5
- liveconsole-1.4.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
6
- liveconsole-1.4.0.dist-info/top_level.txt,sha256=0gva5OCe9lWcj5T88SwGimDDbqsdVqIGQNs98NBH1K0,21
7
- liveconsole-1.4.0.dist-info/RECORD,,
@@ -1,2 +0,0 @@
1
- __init__
2
- liveConsole