liveConsole 1.4.0__py3-none-any.whl → 1.5.0__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.5.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
@@ -11,9 +11,10 @@ Requires-Dist: customtkinter
11
11
  Requires-Dist: pygments
12
12
  Dynamic: license-file
13
13
 
14
- # Live Interactive Python Console
14
+ # PYSOLE
15
15
 
16
16
  ## You can finally test your code in real time without using idle!
17
+ ### If you found [this repository](https://github.com/TzurSoffer/LiveDebugger) useful, please give it a ⭐!.
17
18
 
18
19
  A fully-featured, **live Python console GUI** built with **CustomTkinter** and **Tkinter**, featuring:
19
20
 
@@ -27,7 +28,10 @@ A fully-featured, **live Python console GUI** built with **CustomTkinter** and *
27
28
 
28
29
  * Multi-line input with auto-indentation
29
30
 
30
- * Clickable history of previous commands
31
+ * History of previous commands
32
+
33
+ * **Integrated Help Panel** for quick access to Python object documentation
34
+
31
35
 
32
36
  ## Installation
33
37
 
@@ -77,6 +81,17 @@ A fully-featured, **live Python console GUI** built with **CustomTkinter** and *
77
81
  * Click to copy them back to the prompt for editing or re-execution.
78
82
 
79
83
 
84
+ ### Help Panel
85
+
86
+ * A resizable right-hand panel that displays Python documentation (help()) for any object.
87
+
88
+ * Opens when clicking ctrl+click on a function/method and can be closed with the "X" button.
89
+
90
+ * Scrollable and syntax-styled.
91
+
92
+ * Perfect for quick reference without leaving the console.
93
+
94
+
80
95
  ### Easy Integration
81
96
 
82
97
  * Automatically grabs **caller frame globals and locals** if not provided.
@@ -86,8 +101,8 @@ A fully-featured, **live Python console GUI** built with **CustomTkinter** and *
86
101
  ## Usage
87
102
 
88
103
  ```
89
- from liveConsole import InteractiveConsole
90
- InteractiveConsole().probe()
104
+ import pysole
105
+ pysole.probe()
91
106
  ```
92
107
 
93
108
  * Type Python commands in the `>>>` prompt and see live output.
@@ -0,0 +1,14 @@
1
+ __init__.py,sha256=YbwIrtllO_QzxGjBzZfQp45oA2xI0sU9mXRyCq28wVE,44
2
+ commandHistory.py,sha256=xJtWbJ_vgJo2QGgaZJsApTOi_Hm8Yz0V9_zqQUCItj8,1084
3
+ helpTab.py,sha256=5Fxj_wTNTbuF6sBIN8GdbE6i_zbAEdlrFnbfhnDk5ms,2360
4
+ liveConsole.py,sha256=dqYaCvBs_aDfqrbhXYBp5vLkksmAiUv0DWwsbu1R3B4,167
5
+ mainConsole.py,sha256=MxgJJihgbPtKZ8FqXSFczj5neRVGRiM8RX4lcm0ewk0,12973
6
+ pysole.py,sha256=WFN08BWn9qyhwM7IAa51kCqsYVnuNXVDb1zfv2xw-NE,3924
7
+ styledTextbox.py,sha256=pc-7gaq_pGTZGZmtr_ARbPuKlKgJYqzD6HTR7tFhx7k,1989
8
+ suggestionManager.py,sha256=GRde3c1gFAWt_3rvBoFt_-Xl0aOzzloBMD7MHJA2W8U,6256
9
+ liveconsole-1.5.0.dist-info/licenses/LICENSE,sha256=7dZ0zL72aGaFE0C9DxacOpnaSkC5jajhG6iL7lqhWmU,1064
10
+ liveconsole-1.5.0.dist-info/METADATA,sha256=YLJyN0amuDNkmjan5MvPum-Dz-w04AeNt0lPI7kOS9Y,3681
11
+ liveconsole-1.5.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
12
+ liveconsole-1.5.0.dist-info/entry_points.txt,sha256=IxAXIuGYHQpRflT9VoCLq1eZ3fOXrY_azhk8hcl8eGY,49
13
+ liveconsole-1.5.0.dist-info/top_level.txt,sha256=yEmcEVam34LgZbQWtT6RdrdJVzsXwElx6Wpsn3eR2as,95
14
+ liveconsole-1.5.0.dist-info/RECORD,,
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ liveconsole = liveConsole:main
@@ -0,0 +1,8 @@
1
+ __init__
2
+ commandHistory
3
+ helpTab
4
+ liveConsole
5
+ mainConsole
6
+ pysole
7
+ styledTextbox
8
+ 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()
pysole.py ADDED
@@ -0,0 +1,132 @@
1
+ import customtkinter as ctk
2
+ import inspect
3
+ import sys
4
+ import io
5
+
6
+ from helpTab import HelpTab
7
+ from mainConsole import InteractiveConsoleText
8
+
9
+
10
+ class StdoutRedirect(io.StringIO):
11
+ """Redirects stdout/stderr to a callback function."""
12
+
13
+ def __init__(self, writeCallback):
14
+ super().__init__()
15
+ self.writeCallback = writeCallback
16
+
17
+ def write(self, s):
18
+ if s.strip():
19
+ self.writeCallback(s, "output")
20
+
21
+ def flush(self):
22
+ pass
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
+
30
+ def readline(self, *args, **kwargs):
31
+ return(self.readCallback())
32
+
33
+
34
+ class InteractiveConsole(ctk.CTk):
35
+ """Main console window application."""
36
+
37
+ def __init__(self, userGlobals=None, userLocals=None):
38
+ super().__init__()
39
+
40
+ # Window setup
41
+ self.title("Live Interactive Console")
42
+ self.geometry("900x600")
43
+
44
+ ctk.set_appearance_mode("dark")
45
+ ctk.set_default_color_theme("blue")
46
+
47
+ # Get namespace from caller if not provided
48
+ if userGlobals is None or userLocals is None:
49
+ callerFrame = inspect.currentframe().f_back
50
+ if userGlobals is None:
51
+ userGlobals = callerFrame.f_globals
52
+ if userLocals is None:
53
+ userLocals = callerFrame.f_locals
54
+
55
+ self.userGlobals = userGlobals
56
+ self.userLocals = userLocals
57
+
58
+ # Create UI
59
+ self._createUi()
60
+
61
+ # Redirect stdout/stderr
62
+ self._setupOutputRedirect()
63
+ self._setupInputRedirect()
64
+
65
+ def _createUi(self):
66
+ """Create UI with console and help tab."""
67
+ frame = ctk.CTkFrame(self)
68
+ frame.pack(padx=10, pady=10, fill="both", expand=True)
69
+
70
+ # Horizontal frame
71
+ self.horizFrame = ctk.CTkFrame(frame)
72
+ self.horizFrame.pack(fill="both", expand=True)
73
+
74
+ # Right: Help Tab
75
+ self.helpTab = HelpTab(self.horizFrame, width=500)
76
+
77
+ # Left: Console
78
+ self.consoleFrame = ctk.CTkFrame(self.horizFrame, width=600)
79
+ self.consoleFrame.pack(side="left", fill="both", expand=True)
80
+ self.consoleFrame.pack_propagate(False) # prevent shrinking to fit contents
81
+
82
+ self.console = InteractiveConsoleText(
83
+ self.consoleFrame,
84
+ self.helpTab,
85
+ userGlobals=self.userGlobals,
86
+ userLocals=self.userLocals,
87
+ wrap="word",
88
+ bg="#1e1e1e",
89
+ fg="white",
90
+ insertbackground="white",
91
+ font=("Consolas", 12)
92
+ )
93
+ self.console.pack(fill="both", expand=True, padx=5, pady=5)
94
+ self.console.master = self
95
+
96
+
97
+ def _setupOutputRedirect(self):
98
+ """Setup stdout/stderr redirection to console."""
99
+ sys.stdout = StdoutRedirect(self.console.writeOutput)
100
+ sys.stderr = StdoutRedirect(
101
+ lambda text, tag: self.console.writeOutput(text, "error")
102
+ )
103
+
104
+ def _setupInputRedirect(self):
105
+ """Setup stdin redirection to console."""
106
+ sys.stdin = StdinRedirect(self.console.readInput)
107
+
108
+ def probe(self, *args, **kwargs):
109
+ """Start the console main loop."""
110
+ self.mainloop(*args, **kwargs)
111
+
112
+ def probe():
113
+ InteractiveConsole().probe()
114
+
115
+ def main():
116
+ from pysole import probe
117
+ probe()
118
+
119
+ # Example usage
120
+ if __name__ == "__main__":
121
+ # Example variables and functions for testing
122
+ foo = 42
123
+
124
+ def greet(name):
125
+ print(f"Hello {name}!")
126
+ return(f"Greeted {name}")
127
+
128
+ # Create the list for testing autocomplete
129
+ exampleList = [1, 2, 3, 4, 5]
130
+
131
+ # Start the console
132
+ probe()
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)