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.
liveConsole.py CHANGED
@@ -1,15 +1,10 @@
1
1
  import customtkinter as ctk
2
- import tkinter as tk
3
- import traceback
4
2
  import inspect
5
- import threading
6
3
  import sys
7
4
  import io
8
- import pygments
9
- from pygments.lexers import PythonLexer
10
- from pygments.styles import get_style_by_name
11
- import keyword
12
- import builtins
5
+
6
+ from helpTab import HelpTab
7
+ from mainConsole import InteractiveConsoleText
13
8
 
14
9
 
15
10
  class StdoutRedirect(io.StringIO):
@@ -26,641 +21,15 @@ class StdoutRedirect(io.StringIO):
26
21
  def flush(self):
27
22
  pass
28
23
 
24
+ class StdinRedirect(io.StringIO):
25
+ """Redirects stdin to capture input() from the console."""
26
+ def __init__(self, readCallback):
27
+ super().__init__()
28
+ self.readCallback = readCallback
29
29
 
30
- class CodeSuggestionManager:
31
- """Manages code suggestions and autocomplete functionality."""
32
-
33
- def __init__(self, textWidget, userLocals, userGlobals):
34
- self.userLocals = userLocals
35
- self.userGlobals = userGlobals
36
- self.textWidget = textWidget
37
- self.suggestionWindow = None
38
- self.suggestionListbox = None
39
- self.suggestions = []
40
- self.selectedSuggestion = 0
41
-
42
- # Build suggestion sources
43
- self.keywords = keyword.kwlist
44
- self.builtins = [name for name in dir(builtins) if not name.startswith('_')]
45
-
46
- def getCurrentWord(self):
47
- """Extract the word being typed at cursor position and suggest dir() if applicable."""
48
- suggestions = []
49
- cursorPos = self.textWidget.index(tk.INSERT)
50
- lineStart = self.textWidget.index(f"{cursorPos} linestart")
51
- currentLine = self.textWidget.get(lineStart, cursorPos)
52
-
53
- # Find the current word
54
- words = currentLine.split()
55
- if not words:
56
- return "", suggestions
57
-
58
- currentWord = words[-1]
59
-
60
-
61
- # If the word contains a dot, try to evaluate the base and get its dir()
62
- if '.' in currentWord:
63
- try:
64
- base_expr = '.'.join(currentWord.split('.')[:-1])
65
- obj = eval(base_expr, self.userLocals, self.userGlobals)
66
- suggestions = dir(obj)
67
- except:
68
- pass
69
- for char in "([{,.":
70
- if char in currentWord:
71
- currentWord = currentWord.split(char)[-1]
72
-
73
- return currentWord, suggestions
74
-
75
- def getSuggestions(self, partialWord, suggestions=[]):
76
- """Get code suggestions for partial word."""
77
- if len(partialWord) < 2:
78
- return suggestions
79
-
80
- if suggestions != []:
81
- suggestions = [suggestion for suggestion in suggestions if suggestion.lower().startswith(partialWord.lower())]
82
- else:
83
- # Add matching keywords
84
- for kw in self.keywords:
85
- if kw.startswith(partialWord.lower()):
86
- suggestions.append(kw)
87
-
88
- # Add matching builtins
89
- for builtin in self.builtins:
90
- if builtin.startswith(partialWord):
91
- suggestions.append(builtin)
92
-
93
- # Add matching variables from namespace
94
- master = self.textWidget.master
95
- if hasattr(master, 'userLocals'):
96
- for var in master.userLocals:
97
- if var.startswith(partialWord) and not var.startswith('_'):
98
- suggestions.append(var)
99
-
100
- if hasattr(master, 'userGlobals'):
101
- for var in master.userGlobals:
102
- if var.startswith(partialWord) and not var.startswith('_'):
103
- suggestions.append(var)
104
-
105
- # Remove duplicates and sort
106
- return sorted(list(set(suggestions)))
107
-
108
- def showSuggestions(self):
109
- """Display the suggestions popup."""
110
- currentWord, extraSuggestions = self.getCurrentWord()
111
- suggestions = self.getSuggestions(currentWord, extraSuggestions)
112
-
113
- if not suggestions:
114
- self.hideSuggestions()
115
- return
116
-
117
- self.suggestions = suggestions
118
- self.selectedSuggestion = 0
119
-
120
- # Create suggestion window if needed
121
- if not self.suggestionWindow:
122
- self._createSuggestionWindow()
123
-
124
- # Update listbox content
125
- self.suggestionListbox.delete(0, tk.END)
126
- for suggestion in suggestions:
127
- self.suggestionListbox.insert(tk.END, suggestion)
128
-
129
- self.suggestionListbox.selection_set(0)
130
-
131
- # Position window near cursor
132
- self._positionSuggestionWindow()
133
- self.suggestionWindow.deiconify()
134
-
135
- def _createSuggestionWindow(self):
136
- """Create the suggestion popup window."""
137
- self.suggestionWindow = tk.Toplevel(self.textWidget)
138
- self.suggestionWindow.wm_overrideredirect(True)
139
- self.suggestionWindow.configure(bg="#2d2d2d")
140
-
141
- self.suggestionListbox = tk.Listbox(
142
- self.suggestionWindow,
143
- bg="#2d2d2d",
144
- fg="white",
145
- selectbackground="#0066cc",
146
- font=("Consolas", 10),
147
- height=8
148
- )
149
- self.suggestionListbox.pack()
150
-
151
- def _positionSuggestionWindow(self):
152
- """Position the suggestion window near the cursor."""
153
- cursorPos = self.textWidget.index(tk.INSERT)
154
- x, y, _, _ = self.textWidget.bbox(cursorPos)
155
- x += self.textWidget.winfo_rootx()
156
- y += self.textWidget.winfo_rooty() + 20
157
- self.suggestionWindow.geometry(f"+{x}+{y}")
158
-
159
- def hideSuggestions(self):
160
- """Hide the suggestions popup."""
161
- if self.suggestionWindow:
162
- self.suggestionWindow.withdraw()
163
-
164
- def applySuggestion(self, suggestion=None):
165
- """Apply the selected suggestion at cursor position."""
166
- if not suggestion and self.suggestions:
167
- suggestion = self.suggestions[self.selectedSuggestion]
168
- if not suggestion:
169
- return
170
-
171
- currentWord, _ = self.getCurrentWord()
172
- # Only insert the missing part
173
- missingPart = suggestion[len(currentWord):]
174
- cursorPos = self.textWidget.index(tk.INSERT)
175
- self.textWidget.insert(cursorPos, missingPart)
176
-
177
- self.hideSuggestions()
178
-
179
- def handleNavigation(self, direction):
180
- """Handle up/down navigation in suggestions."""
181
- if not self.suggestions:
182
- return
183
-
184
- if direction == "down":
185
- self.selectedSuggestion = min(self.selectedSuggestion + 1, len(self.suggestions) - 1)
186
- else: # up
187
- self.selectedSuggestion = max(self.selectedSuggestion - 1, 0)
188
-
189
- self.suggestionListbox.selection_clear(0, tk.END)
190
- self.suggestionListbox.selection_set(self.selectedSuggestion)
191
-
192
-
193
- class CommandHistory:
194
- """Manages command history and navigation."""
195
-
196
- def __init__(self):
197
- self.history = []
198
- self.index = -1
199
- self.tempCommand = ""
200
-
201
- def add(self, command):
202
- """Add a command to history."""
203
- if command.strip():
204
- self.history.append(command)
205
- self.index = len(self.history)
206
-
207
- def navigateUp(self):
208
- """Get previous command from history."""
209
- if self.index > 0:
210
- self.index -= 1
211
- return self.history[self.index]
212
- return None
213
-
214
- def navigateDown(self):
215
- """Get next command from history."""
216
- if self.index < len(self.history) - 1:
217
- self.index += 1
218
- return self.history[self.index]
219
- elif self.index == len(self.history) - 1:
220
- self.index = len(self.history)
221
- return self.tempCommand
222
- return None
223
-
224
- def setTemp(self, command):
225
- """Store temporary command while navigating history."""
226
- self.tempCommand = command
227
-
228
-
229
- class StyledTextWindow(tk.Text):
230
- def __init__(self, master, **kwargs):
231
- super().__init__(master, **kwargs)
232
-
233
- # Syntax highlighting setup
234
- self.lexer = PythonLexer()
235
- self.style = get_style_by_name("monokai")
236
-
237
- # Setup tags
238
- self._setupTags()
239
-
240
- def _setupTags(self):
241
- """Configure text tags for different output types."""
242
- self.tag_configure("prompt", foreground="#00ff00", font=("Consolas", 12, "bold"))
243
- self.tag_configure("output", foreground="#ffffff", font=("Consolas", 12))
244
- self.tag_configure("error", foreground="#ff6666", font=("Consolas", 12))
245
- self.tag_configure("result", foreground="#66ccff", font=("Consolas", 12))
246
-
247
- # Configure syntax highlighting tags
248
- for token, style in self.style:
249
- if style["color"]:
250
- fg = f"#{style['color']}"
251
- font = ("Consolas", 12, "bold" if style["bold"] else "normal")
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"""
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
-
302
- def _setupBindings(self):
303
- """Setup all key and mouse bindings."""
304
- self.bind("<Return>", self.onEnter)
305
- self.bind("<Shift-Return>", self.onShiftEnter)
306
- self.bind("<Control-c>", self.cancel)
307
- self.bind("<Tab>", self.onTab)
308
- self.bind("<BackSpace>", self.onBackspace)
309
- self.bind("<KeyRelease>", self.onKeyRelease)
310
- self.bind("<KeyPress>", self.onKeyPress)
311
- self.bind("<Button-1>", self.onClick)
312
- self.bind("<Up>", self.onUp)
313
- self.bind("<Down>", self.onDown)
314
-
315
- def getCurrentLineNumber(self):
316
- """Get the line number where current command starts."""
317
- return int(self.index("end-1c").split(".")[0])
318
-
319
- def getCommandStartPosition(self):
320
- """Get the starting position of the current command."""
321
- return f"{self.currentCommandLine}.0"
322
-
323
- def replaceCurrentCommand(self, newCommand):
324
- """Replace the current command with new text."""
325
- if self.isExecuting:
326
- return
327
-
328
- start = self.getPromptPosition()
329
- end = "end-1c"
330
-
331
- self.delete(start, end)
332
- self.insert(start, newCommand)
333
- self.see("end")
334
-
335
- def isCursorInEditableArea(self):
336
- """Check if cursor is in the editable command area."""
337
- if self.isExecuting:
338
- return False
339
-
340
- cursorLine = int(self.index("insert").split(".")[0])
341
- cursorCol = int(self.index("insert").split(".")[1])
342
-
343
- return (cursorLine >= self.currentCommandLine and
344
- (cursorLine > self.currentCommandLine or cursorCol >= self.PROMPT_LENGTH))
345
-
346
- def onEnter(self, event):
347
- """Handle Enter key - execute command."""
348
- self.suggestionManager.hideSuggestions()
349
-
350
- if self.isExecuting:
351
- return "break"
352
-
353
- command = self.getCurrentCommand()
354
-
355
- if not command.strip():
356
- return "break"
357
-
358
- # Check if statement is incomplete
359
- if self.isIncompleteStatement(command):
360
- return self.onShiftEnter(event)
361
-
362
- # Execute the command
363
- self.history.add(command)
364
- self.mark_set("insert", "end")
365
- self.insert("end", "\n")
366
- self.see("end")
367
-
368
- # Execute in thread
369
- self.isExecuting = True
370
- threading.Thread(
371
- target=self.executeCommandThreaded,
372
- args=(command,),
373
- daemon=True
374
- ).start()
375
-
376
- return "break"
377
-
378
- def onShiftEnter(self, event):
379
- """Handle Shift+Enter - new line with auto-indent."""
380
- self.suggestionManager.hideSuggestions()
381
-
382
- if self.isExecuting:
383
- return "break"
384
-
385
- # Get current line for indent calculation
386
- cursorPos = self.index("insert")
387
- lineStart = self.index(f"{cursorPos} linestart")
388
- lineEnd = self.index(f"{cursorPos} lineend")
389
- currentLine = self.get(lineStart, lineEnd)
390
-
391
- # Calculate indentation
392
- indent = self.calculateIndent(currentLine)
393
-
394
- # Insert newline with indent
395
- self.insert("insert", "\n" + " " * indent)
396
- self.see("end")
397
-
398
- return "break"
399
-
400
- def onTab(self, event):
401
- """Handle Tab key for autocompletion."""
402
- if self.isExecuting:
403
- return "break"
404
-
405
- if self.suggestionManager.suggestionWindow and \
406
- self.suggestionManager.suggestionWindow.winfo_viewable():
407
- self.suggestionManager.applySuggestion()
408
- else:
409
- self.suggestionManager.showSuggestions()
410
-
411
- return "break"
412
-
413
- def onBackspace(self, event):
414
- """Prevent backspace from deleting the prompt."""
415
- if not self.isCursorInEditableArea():
416
- return "break"
417
-
418
- # Check if we're at the prompt boundary
419
- cursorPos = self.index("insert")
420
- promptPos = self.getPromptPosition()
421
-
422
- if self.compare(cursorPos, "<=", promptPos):
423
- return "break"
424
-
425
- def onClick(self, event):
426
- """Handle mouse clicks - Ctrl+Click opens help for the clicked word."""
427
- self.suggestionManager.hideSuggestions()
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
456
-
457
- def onKeyPress(self, event):
458
- """Handle key press events."""
459
- # print(event.keysym)
460
- if self.suggestionManager.suggestionWindow and \
461
- self.suggestionManager.suggestionWindow.winfo_viewable():
462
- if event.keysym == "Escape":
463
- self.suggestionManager.hideSuggestions()
464
- return "break"
465
-
466
- # Prevent editing outside command area
467
- if not event.keysym in ["Shift_L", "Shift_R", "Control_L", "Control_R"]:
468
- self.navigatingHistory = False
469
- if not self.isCursorInEditableArea():
470
- self.mark_set("insert", "end")
471
-
472
- if event.keysym in ["Left", "Right"]:
473
- if self.index("insert") == self.getPromptPosition():
474
- self.mark_set("insert", "1.4")
475
- return "break"
476
-
477
- def onKeyRelease(self, event):
478
- """Handle key release events."""
479
- if event.keysym in ["Return", "Escape", "Left", "Right", "Home", "End"]:
480
- self.suggestionManager.hideSuggestions()
481
- elif event.keysym not in ["Up", "Down", "Shift_L", "Shift_R", "Control_L", "Control_R"]:
482
- if not self.isExecuting:
483
- self.after_idle(self.suggestionManager.showSuggestions)
484
- if not self.isExecuting:
485
- self.after_idle(lambda: self.updateStyling(start=self.getPromptPosition()))
486
-
487
- def cancel(self, event):
488
- self.history.add(self.getCurrentCommand())
489
- self.replaceCurrentCommand("")
490
-
491
- def historyReplace(self, command):
492
- if self.getCurrentCommand() == "" or self.navigatingHistory:
493
- if self.isExecuting:
494
- return "break"
495
-
496
- if self.history.index == len(self.history.history):
497
- self.history.setTemp(self.getCurrentCommand())
498
-
499
- if command is not None:
500
- self.replaceCurrentCommand(command)
501
- self.navigatingHistory = True
502
- return("break")
503
-
504
- def onUp(self, event):
505
- if self.suggestionManager.suggestionWindow and \
506
- self.suggestionManager.suggestionWindow.winfo_viewable():
507
- if event.keysym == "Up":
508
- self.suggestionManager.handleNavigation("up")
509
- return "break"
510
- command = self.history.navigateUp()
511
- return(self.historyReplace(command))
512
- # self.mark_set("insert", "insert -1 line")
513
-
514
- def onDown(self, event):
515
- if self.suggestionManager.suggestionWindow and \
516
- self.suggestionManager.suggestionWindow.winfo_viewable():
517
- if event.keysym == "Down":
518
- self.suggestionManager.handleNavigation("down")
519
- return "break"
520
- command = self.history.navigateDown()
521
- return(self.historyReplace(command))
522
-
523
- def isIncompleteStatement(self, code):
524
- """Check if the code is an incomplete statement."""
525
- lines = code.split("\n")
526
- if not lines[-1].strip():
527
- return False
528
-
529
- # Check for line ending with colon
530
- for line in lines:
531
- if line.strip().endswith(":"):
532
- return True
533
-
534
- return False
535
-
536
- def calculateIndent(self, line):
537
- """Calculate the indentation level for the next line."""
538
- currentIndent = len(line) - len(line.lstrip())
539
-
540
- # If line ends with colon, increase indent
541
- if line.strip().endswith(":"):
542
- return currentIndent + 4
543
-
544
- return currentIndent
545
-
546
- def writeOutput(self, text, tag="output"):
547
- """Write output to the console (thread-safe)."""
548
- def _write():
549
- self.insert("end", text + "\n", tag)
550
- self.see("end")
551
-
552
- self.after(0, _write)
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
-
564
- def addPrompt(self):
565
- """Add a new command prompt."""
566
- def _add():
567
- # Store the line number for the new command
568
- self.currentCommandLine = self.getCurrentLineNumber()
569
-
570
- # Insert prompt
571
- self.insert("end", self.PROMPT)
572
- promptStart = f"{self.currentCommandLine}.0"
573
- promptEnd = f"{self.currentCommandLine}.{self.PROMPT_LENGTH}"
574
- self.tag_add("prompt", promptStart, promptEnd)
575
-
576
- self.mark_set("insert", "end")
577
- self.see("end")
578
- self.isExecuting = False
579
-
580
- if self.isExecuting:
581
- self.after(0, _add)
582
- else:
583
- _add()
584
-
585
- def executeCommandThreaded(self, command):
586
- """Execute a command in a separate thread."""
587
- try:
588
- # Try eval first for expressions
589
- result = eval(command, self.master.userGlobals, self.master.userLocals)
590
- if result is not None:
591
- self.writeOutput(str(result), "result")
592
- self.master.userLocals["_"] = result
593
- except SyntaxError:
594
- try:
595
- # Try exec for statements
596
- exec(command, self.master.userGlobals, self.master.userLocals)
597
- except Exception:
598
- self.writeOutput(traceback.format_exc(), "error")
599
- except Exception:
600
- self.writeOutput(traceback.format_exc(), "error")
601
-
602
- # Add new prompt after execution
603
- self.addPrompt()
604
-
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."""
30
+ def readline(self, *args, **kwargs):
31
+ return(self.readCallback())
658
32
 
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
33
 
665
34
  class InteractiveConsole(ctk.CTk):
666
35
  """Main console window application."""
@@ -691,6 +60,7 @@ class InteractiveConsole(ctk.CTk):
691
60
 
692
61
  # Redirect stdout/stderr
693
62
  self._setupOutputRedirect()
63
+ self._setupInputRedirect()
694
64
 
695
65
  def _createUi(self):
696
66
  """Create UI with console and help tab."""
@@ -730,6 +100,10 @@ class InteractiveConsole(ctk.CTk):
730
100
  sys.stderr = StdoutRedirect(
731
101
  lambda text, tag: self.console.writeOutput(text, "error")
732
102
  )
103
+
104
+ def _setupInputRedirect(self):
105
+ """Setup stdin redirection to console."""
106
+ sys.stdin = StdinRedirect(self.console.readInput)
733
107
 
734
108
  def probe(self, *args, **kwargs):
735
109
  """Start the console main loop."""
@@ -743,7 +117,7 @@ if __name__ == "__main__":
743
117
 
744
118
  def greet(name):
745
119
  print(f"Hello {name}!")
746
- return f"Greeted {name}"
120
+ return(f"Greeted {name}")
747
121
 
748
122
  # Create the list for testing autocomplete
749
123
  exampleList = [1, 2, 3, 4, 5]