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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: liveConsole
3
- Version: 1.3.2
3
+ Version: 1.4.0
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "liveConsole"
3
- version = "1.3.2"
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"}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: liveConsole
3
- Version: 1.3.2
3
+ Version: 1.4.0
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
@@ -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 InteractiveConsoleText(tk.Text):
231
- """A tk.Text widget with Python syntax highlighting for interactive console."""
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
- # Track current command
250
- self.currentCommandLine = 1
251
- self.isExecuting = False
252
-
253
- # Setup tags and bindings
237
+ # Setup tags
254
238
  self._setupTags()
255
- self._setupBindings()
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 - prevent clicking before prompt."""
426
+ """Handle mouse clicks - Ctrl+Click opens help for the clicked word."""
412
427
  self.suggestionManager.hideSuggestions()
413
- return None
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.after_idle(self.highlightCurrentCommand)
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 the user interface."""
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
- # Console text widget
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
- frame,
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