liveConsole 1.3.2__tar.gz → 1.4.0__tar.gz
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.
- {liveconsole-1.3.2/src/liveConsole.egg-info → liveconsole-1.4.0}/PKG-INFO +1 -1
- {liveconsole-1.3.2 → liveconsole-1.4.0}/pyproject.toml +1 -1
- {liveconsole-1.3.2 → liveconsole-1.4.0/src/liveConsole.egg-info}/PKG-INFO +1 -1
- {liveconsole-1.3.2 → liveconsole-1.4.0}/src/liveConsole.py +173 -77
- {liveconsole-1.3.2 → liveconsole-1.4.0}/LICENSE +0 -0
- {liveconsole-1.3.2 → liveconsole-1.4.0}/README.md +0 -0
- {liveconsole-1.3.2 → liveconsole-1.4.0}/setup.cfg +0 -0
- {liveconsole-1.3.2 → liveconsole-1.4.0}/src/__init__.py +0 -0
- {liveconsole-1.3.2 → liveconsole-1.4.0}/src/liveConsole.egg-info/SOURCES.txt +0 -0
- {liveconsole-1.3.2 → liveconsole-1.4.0}/src/liveConsole.egg-info/dependency_links.txt +0 -0
- {liveconsole-1.3.2 → liveconsole-1.4.0}/src/liveConsole.egg-info/requires.txt +0 -0
- {liveconsole-1.3.2 → liveconsole-1.4.0}/src/liveConsole.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
[project]
|
2
2
|
name = "liveConsole"
|
3
|
-
version = "1.
|
3
|
+
version = "1.4.0"
|
4
4
|
description = "An IDLE-like debugger to allow for real-time command injection for debugging and testing python code"
|
5
5
|
authors = [{ name="Tzur Soffer", email="tzur.soffer@gmail.com" }]
|
6
6
|
license = {text = "MIT"}
|
@@ -76,8 +76,7 @@ class CodeSuggestionManager:
|
|
76
76
|
"""Get code suggestions for partial word."""
|
77
77
|
if len(partialWord) < 2:
|
78
78
|
return suggestions
|
79
|
-
|
80
|
-
# print(partialWord)
|
79
|
+
|
81
80
|
if suggestions != []:
|
82
81
|
suggestions = [suggestion for suggestion in suggestions if suggestion.lower().startswith(partialWord.lower())]
|
83
82
|
else:
|
@@ -227,36 +226,17 @@ class CommandHistory:
|
|
227
226
|
self.tempCommand = command
|
228
227
|
|
229
228
|
|
230
|
-
class
|
231
|
-
|
232
|
-
|
233
|
-
PROMPT = ">>> "
|
234
|
-
PROMPT_LENGTH = 4
|
235
|
-
|
236
|
-
def __init__(self, master, userLocals=None, userGlobals=None, **kwargs):
|
229
|
+
class StyledTextWindow(tk.Text):
|
230
|
+
def __init__(self, master, **kwargs):
|
237
231
|
super().__init__(master, **kwargs)
|
238
|
-
|
239
|
-
# Initialize components
|
240
|
-
self.suggestionManager = CodeSuggestionManager(self, userLocals=userLocals, userGlobals=userGlobals)
|
241
|
-
|
242
|
-
self.navigatingHistory = False
|
243
|
-
self.history = CommandHistory()
|
244
|
-
|
232
|
+
|
245
233
|
# Syntax highlighting setup
|
246
234
|
self.lexer = PythonLexer()
|
247
235
|
self.style = get_style_by_name("monokai")
|
248
236
|
|
249
|
-
#
|
250
|
-
self.currentCommandLine = 1
|
251
|
-
self.isExecuting = False
|
252
|
-
|
253
|
-
# Setup tags and bindings
|
237
|
+
# Setup tags
|
254
238
|
self._setupTags()
|
255
|
-
|
256
|
-
|
257
|
-
# Initialize with first prompt
|
258
|
-
self.addPrompt()
|
259
|
-
|
239
|
+
|
260
240
|
def _setupTags(self):
|
261
241
|
"""Configure text tags for different output types."""
|
262
242
|
self.tag_configure("prompt", foreground="#00ff00", font=("Consolas", 12, "bold"))
|
@@ -270,7 +250,55 @@ class InteractiveConsoleText(tk.Text):
|
|
270
250
|
fg = f"#{style['color']}"
|
271
251
|
font = ("Consolas", 12, "bold" if style["bold"] else "normal")
|
272
252
|
self.tag_configure(str(token), foreground=fg, font=font)
|
253
|
+
|
254
|
+
|
255
|
+
def updateStyling(self, start="1.0"):
|
256
|
+
"""Apply syntax highlighting to the current command."""
|
257
|
+
end = "end-1c"
|
258
|
+
|
259
|
+
for token, _ in self.style:
|
260
|
+
self.tag_remove(str(token), start, end)
|
261
|
+
|
262
|
+
# Get and highlight the command
|
263
|
+
command = self.get(start, "end-1c")
|
264
|
+
if not command:
|
265
|
+
return(-1)
|
266
|
+
|
267
|
+
self.mark_set("highlight_pos", start)
|
268
|
+
|
269
|
+
for token, content in pygments.lex(command, self.lexer):
|
270
|
+
if content:
|
271
|
+
endPos = f"highlight_pos + {len(content)}c"
|
272
|
+
if content.strip(): # Only highlight non-whitespace
|
273
|
+
self.tag_add(str(token), "highlight_pos", endPos)
|
274
|
+
self.mark_set("highlight_pos", endPos)
|
275
|
+
|
276
|
+
class InteractiveConsoleText(StyledTextWindow):
|
277
|
+
"""TBD"""
|
273
278
|
|
279
|
+
PROMPT = ">>> "
|
280
|
+
PROMPT_LENGTH = 4
|
281
|
+
|
282
|
+
def __init__(self, master, helpTab, userLocals=None, userGlobals=None, **kwargs):
|
283
|
+
super().__init__(master, **kwargs)
|
284
|
+
|
285
|
+
# Initialize components
|
286
|
+
self.suggestionManager = CodeSuggestionManager(self, userLocals=userLocals, userGlobals=userGlobals)
|
287
|
+
self.helpTab = helpTab
|
288
|
+
|
289
|
+
self.navigatingHistory = False
|
290
|
+
self.history = CommandHistory()
|
291
|
+
|
292
|
+
# Track current command
|
293
|
+
self.currentCommandLine = 1
|
294
|
+
self.isExecuting = False
|
295
|
+
|
296
|
+
# Setup bindings
|
297
|
+
self._setupBindings()
|
298
|
+
|
299
|
+
# Initialize with first prompt
|
300
|
+
self.addPrompt()
|
301
|
+
|
274
302
|
def _setupBindings(self):
|
275
303
|
"""Setup all key and mouse bindings."""
|
276
304
|
self.bind("<Return>", self.onEnter)
|
@@ -288,23 +316,10 @@ class InteractiveConsoleText(tk.Text):
|
|
288
316
|
"""Get the line number where current command starts."""
|
289
317
|
return int(self.index("end-1c").split(".")[0])
|
290
318
|
|
291
|
-
def getPromptPosition(self):
|
292
|
-
"""Get the position right after the prompt on current command line."""
|
293
|
-
return f"{self.currentCommandLine}.{self.PROMPT_LENGTH}"
|
294
|
-
|
295
319
|
def getCommandStartPosition(self):
|
296
320
|
"""Get the starting position of the current command."""
|
297
321
|
return f"{self.currentCommandLine}.0"
|
298
322
|
|
299
|
-
def getCurrentCommand(self):
|
300
|
-
"""Extract the current command text (without prompt)."""
|
301
|
-
if self.isExecuting:
|
302
|
-
return ""
|
303
|
-
|
304
|
-
start = self.getPromptPosition()
|
305
|
-
end = "end-1c"
|
306
|
-
return self.get(start, end)
|
307
|
-
|
308
323
|
def replaceCurrentCommand(self, newCommand):
|
309
324
|
"""Replace the current command with new text."""
|
310
325
|
if self.isExecuting:
|
@@ -406,11 +421,38 @@ class InteractiveConsoleText(tk.Text):
|
|
406
421
|
|
407
422
|
if self.compare(cursorPos, "<=", promptPos):
|
408
423
|
return "break"
|
409
|
-
|
424
|
+
|
410
425
|
def onClick(self, event):
|
411
|
-
"""Handle mouse clicks -
|
426
|
+
"""Handle mouse clicks - Ctrl+Click opens help for the clicked word."""
|
412
427
|
self.suggestionManager.hideSuggestions()
|
413
|
-
|
428
|
+
|
429
|
+
if event.state & 0x4: #< Ctrl pressed
|
430
|
+
clickIndex = self.index(f"@{event.x},{event.y}") # mouse index
|
431
|
+
|
432
|
+
# Get the full line
|
433
|
+
i = int(clickIndex.split('.')[1])
|
434
|
+
lineNum = clickIndex.split('.')[0]
|
435
|
+
lineStart = f"{lineNum}.0"
|
436
|
+
lineEnd = f"{lineNum}.end"
|
437
|
+
lineText = self.get(lineStart, lineEnd)
|
438
|
+
|
439
|
+
wordEndIndex = self.index(f"{clickIndex} wordend")
|
440
|
+
# obj = self.get(f"{clickIndex} wordstart", f"{clickIndex} wordend").strip() #< Get the word at that index
|
441
|
+
obj = ""
|
442
|
+
for i in range (i-1,2, -1):
|
443
|
+
letter = lineText[i]
|
444
|
+
if (not (letter.isalnum() or letter == "_" or letter == ".")): #< or (letter in " \n\t\r")
|
445
|
+
obj = lineText[i+1: int(wordEndIndex.split('.')[1])]
|
446
|
+
break
|
447
|
+
|
448
|
+
|
449
|
+
if obj:
|
450
|
+
self.helpTab.updateHelp(obj)
|
451
|
+
self.helpTab.open()
|
452
|
+
|
453
|
+
return "break" #< Prevent default cursor behavior
|
454
|
+
|
455
|
+
return None #< Normal click behavior
|
414
456
|
|
415
457
|
def onKeyPress(self, event):
|
416
458
|
"""Handle key press events."""
|
@@ -439,7 +481,8 @@ class InteractiveConsoleText(tk.Text):
|
|
439
481
|
elif event.keysym not in ["Up", "Down", "Shift_L", "Shift_R", "Control_L", "Control_R"]:
|
440
482
|
if not self.isExecuting:
|
441
483
|
self.after_idle(self.suggestionManager.showSuggestions)
|
442
|
-
self.
|
484
|
+
if not self.isExecuting:
|
485
|
+
self.after_idle(lambda: self.updateStyling(start=self.getPromptPosition()))
|
443
486
|
|
444
487
|
def cancel(self, event):
|
445
488
|
self.history.add(self.getCurrentCommand())
|
@@ -499,32 +542,6 @@ class InteractiveConsoleText(tk.Text):
|
|
499
542
|
return currentIndent + 4
|
500
543
|
|
501
544
|
return currentIndent
|
502
|
-
|
503
|
-
def highlightCurrentCommand(self):
|
504
|
-
"""Apply syntax highlighting to the current command."""
|
505
|
-
if self.isExecuting:
|
506
|
-
return
|
507
|
-
|
508
|
-
# Clear existing highlighting
|
509
|
-
start = self.getPromptPosition()
|
510
|
-
end = "end-1c"
|
511
|
-
|
512
|
-
for token, _ in self.style:
|
513
|
-
self.tag_remove(str(token), start, end)
|
514
|
-
|
515
|
-
# Get and highlight the command
|
516
|
-
command = self.getCurrentCommand()
|
517
|
-
if not command:
|
518
|
-
return
|
519
|
-
|
520
|
-
self.mark_set("highlight_pos", start)
|
521
|
-
|
522
|
-
for token, content in pygments.lex(command, self.lexer):
|
523
|
-
if content:
|
524
|
-
endPos = f"highlight_pos + {len(content)}c"
|
525
|
-
if content.strip(): # Only highlight non-whitespace
|
526
|
-
self.tag_add(str(token), "highlight_pos", endPos)
|
527
|
-
self.mark_set("highlight_pos", endPos)
|
528
545
|
|
529
546
|
def writeOutput(self, text, tag="output"):
|
530
547
|
"""Write output to the console (thread-safe)."""
|
@@ -533,7 +550,17 @@ class InteractiveConsoleText(tk.Text):
|
|
533
550
|
self.see("end")
|
534
551
|
|
535
552
|
self.after(0, _write)
|
536
|
-
|
553
|
+
|
554
|
+
def getPromptPosition(self):
|
555
|
+
"""Get the position right after the prompt on current command line."""
|
556
|
+
return f"{self.currentCommandLine}.{self.PROMPT_LENGTH}"
|
557
|
+
|
558
|
+
def getCurrentCommand(self):
|
559
|
+
"""Extract the current command text (without prompt)."""
|
560
|
+
start = self.getPromptPosition()
|
561
|
+
end = "end-1c"
|
562
|
+
return self.get(start, end)
|
563
|
+
|
537
564
|
def addPrompt(self):
|
538
565
|
"""Add a new command prompt."""
|
539
566
|
def _add():
|
@@ -576,6 +603,65 @@ class InteractiveConsoleText(tk.Text):
|
|
576
603
|
self.addPrompt()
|
577
604
|
|
578
605
|
|
606
|
+
class HelpTab(ctk.CTkFrame):
|
607
|
+
"""A right-hand help tab with closable and updateable text content."""
|
608
|
+
|
609
|
+
def __init__(self, parent, width=500, title="Help", **kwargs):
|
610
|
+
super().__init__(parent, width=width, **kwargs)
|
611
|
+
self.parent = parent
|
612
|
+
self.visible = False
|
613
|
+
|
614
|
+
# Ensure initial width is respected
|
615
|
+
self.pack_propagate(False)
|
616
|
+
|
617
|
+
# Header frame with title and close button
|
618
|
+
headerFrame = ctk.CTkFrame(self, height=30)
|
619
|
+
headerFrame.pack(fill="x")
|
620
|
+
self.style = get_style_by_name("monokai")
|
621
|
+
|
622
|
+
self.titleLabel = ctk.CTkLabel(headerFrame, text=title, font=("Consolas", 12, "bold"))
|
623
|
+
self.titleLabel.pack(side="left", padx=5)
|
624
|
+
|
625
|
+
self.closeButton = ctk.CTkButton(headerFrame, text="X", height=20, command=self.close)
|
626
|
+
self.closeButton.pack(side="right", padx=5)
|
627
|
+
|
628
|
+
# Scrollable text area
|
629
|
+
self.textBox = StyledTextWindow(self, wrap="word", font=("Consolas", 11), bg="#2e2e2e")
|
630
|
+
self.textBox.pack(fill="both", expand=True, padx=5, pady=5)
|
631
|
+
self.textBox.configure(state="disabled") # read-only
|
632
|
+
|
633
|
+
def close(self):
|
634
|
+
"""Hide the help tab."""
|
635
|
+
if self.visible:
|
636
|
+
self.pack_forget()
|
637
|
+
self.visible = False
|
638
|
+
|
639
|
+
def open(self):
|
640
|
+
"""Show the help tab."""
|
641
|
+
if not self.visible:
|
642
|
+
self.pack(side="left", fill="y")
|
643
|
+
# self.configure(width=self.minWidth)
|
644
|
+
self.visible = True
|
645
|
+
|
646
|
+
def _getHelp(self, obj):
|
647
|
+
"""Return the output of help(obj) as a string."""
|
648
|
+
old_stdout = sys.stdout # save current stdout
|
649
|
+
sys.stdout = buffer = io.StringIO() # redirect stdout to a string buffer
|
650
|
+
try:
|
651
|
+
help(obj)
|
652
|
+
return buffer.getvalue()
|
653
|
+
finally:
|
654
|
+
sys.stdout = old_stdout # restore original stdout
|
655
|
+
|
656
|
+
def updateHelp(self, obj):
|
657
|
+
"""Update the help tab content."""
|
658
|
+
|
659
|
+
self.textBox.configure(state="normal")
|
660
|
+
self.textBox.delete("1.0", "end")
|
661
|
+
self.textBox.insert("1.0", self._getHelp(obj))
|
662
|
+
self.textBox.updateStyling()
|
663
|
+
self.textBox.configure(state="disabled")
|
664
|
+
|
579
665
|
class InteractiveConsole(ctk.CTk):
|
580
666
|
"""Main console window application."""
|
581
667
|
|
@@ -607,14 +693,25 @@ class InteractiveConsole(ctk.CTk):
|
|
607
693
|
self._setupOutputRedirect()
|
608
694
|
|
609
695
|
def _createUi(self):
|
610
|
-
"""Create
|
611
|
-
# Main frame
|
696
|
+
"""Create UI with console and help tab."""
|
612
697
|
frame = ctk.CTkFrame(self)
|
613
698
|
frame.pack(padx=10, pady=10, fill="both", expand=True)
|
614
|
-
|
615
|
-
#
|
699
|
+
|
700
|
+
# Horizontal frame
|
701
|
+
self.horizFrame = ctk.CTkFrame(frame)
|
702
|
+
self.horizFrame.pack(fill="both", expand=True)
|
703
|
+
|
704
|
+
# Right: Help Tab
|
705
|
+
self.helpTab = HelpTab(self.horizFrame, width=500)
|
706
|
+
|
707
|
+
# Left: Console
|
708
|
+
self.consoleFrame = ctk.CTkFrame(self.horizFrame, width=600)
|
709
|
+
self.consoleFrame.pack(side="left", fill="both", expand=True)
|
710
|
+
self.consoleFrame.pack_propagate(False) # prevent shrinking to fit contents
|
711
|
+
|
616
712
|
self.console = InteractiveConsoleText(
|
617
|
-
|
713
|
+
self.consoleFrame,
|
714
|
+
self.helpTab,
|
618
715
|
userGlobals=self.userGlobals,
|
619
716
|
userLocals=self.userLocals,
|
620
717
|
wrap="word",
|
@@ -624,9 +721,8 @@ class InteractiveConsole(ctk.CTk):
|
|
624
721
|
font=("Consolas", 12)
|
625
722
|
)
|
626
723
|
self.console.pack(fill="both", expand=True, padx=5, pady=5)
|
627
|
-
|
628
|
-
# Give console access to namespace
|
629
724
|
self.console.master = self
|
725
|
+
|
630
726
|
|
631
727
|
def _setupOutputRedirect(self):
|
632
728
|
"""Setup stdout/stderr redirection to console."""
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|