mirror of
https://github.com/bec-project/bec_widgets.git
synced 2025-07-14 03:31:50 +02:00
feat(console): add "prompt" signal to inform when shell is at prompt
This commit is contained in:
@ -10,9 +10,11 @@ import fcntl
|
|||||||
import html
|
import html
|
||||||
import os
|
import os
|
||||||
import pty
|
import pty
|
||||||
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import pyte
|
import pyte
|
||||||
|
from pygments.token import Token
|
||||||
from pyte.screens import History
|
from pyte.screens import History
|
||||||
from qtpy import QtCore, QtGui, QtWidgets
|
from qtpy import QtCore, QtGui, QtWidgets
|
||||||
from qtpy.QtCore import Property as pyqtProperty
|
from qtpy.QtCore import Property as pyqtProperty
|
||||||
@ -235,10 +237,14 @@ class BECConsole(QtWidgets.QWidget):
|
|||||||
PLUGIN = True
|
PLUGIN = True
|
||||||
ICON_NAME = "terminal"
|
ICON_NAME = "terminal"
|
||||||
|
|
||||||
|
prompt = pyqtSignal(bool)
|
||||||
|
|
||||||
def __init__(self, parent=None, cols=132):
|
def __init__(self, parent=None, cols=132):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
|
||||||
self.term = _TerminalWidget(self, cols, rows=43)
|
self.term = _TerminalWidget(self, cols, rows=43)
|
||||||
|
self.term.prompt.connect(self.prompt) # forward signal from term to this widget
|
||||||
|
|
||||||
self.scroll_bar = QScrollBar(Qt.Vertical, self)
|
self.scroll_bar = QScrollBar(Qt.Vertical, self)
|
||||||
# self.scroll_bar.hide()
|
# self.scroll_bar.hide()
|
||||||
layout = QHBoxLayout(self)
|
layout = QHBoxLayout(self)
|
||||||
@ -320,9 +326,32 @@ class BECConsole(QtWidgets.QWidget):
|
|||||||
def start(self, deactivate_ctrl_d=True):
|
def start(self, deactivate_ctrl_d=True):
|
||||||
self.term.start(deactivate_ctrl_d=deactivate_ctrl_d)
|
self.term.start(deactivate_ctrl_d=deactivate_ctrl_d)
|
||||||
|
|
||||||
def push(self, text):
|
def push(self, text, hit_return=False):
|
||||||
"""Push some text to the terminal"""
|
"""Push some text to the terminal"""
|
||||||
return self.term.push(text)
|
return self.term.push(text, hit_return=hit_return)
|
||||||
|
|
||||||
|
def execute_command(self, command):
|
||||||
|
self.push(command, hit_return=True)
|
||||||
|
|
||||||
|
def set_prompt_tokens(self, *tokens):
|
||||||
|
"""Prepare regexp to identify prompt, based on tokens
|
||||||
|
|
||||||
|
Tokens are returned from get_ipython().prompts.in_prompt_tokens()
|
||||||
|
"""
|
||||||
|
regex_parts = []
|
||||||
|
for token_type, token_value in tokens:
|
||||||
|
if token_type == Token.PromptNum: # Handle dynamic prompt number
|
||||||
|
regex_parts.append(r"[\d\?]+") # Match one or more digits or '?'
|
||||||
|
else:
|
||||||
|
# Escape other prompt parts (e.g., "In [", "]: ")
|
||||||
|
if not token_value:
|
||||||
|
regex_parts.append(".+?") # arbitrary string
|
||||||
|
else:
|
||||||
|
regex_parts.append(re.escape(token_value))
|
||||||
|
|
||||||
|
# Combine into a single regex
|
||||||
|
prompt_pattern = "".join(regex_parts)
|
||||||
|
self.term._prompt_re = re.compile(prompt_pattern + r"\s*$")
|
||||||
|
|
||||||
cols = pyqtProperty(int, get_cols, set_cols)
|
cols = pyqtProperty(int, get_cols, set_cols)
|
||||||
rows = pyqtProperty(int, get_rows, set_rows)
|
rows = pyqtProperty(int, get_rows, set_rows)
|
||||||
@ -336,7 +365,13 @@ class _TerminalWidget(QtWidgets.QPlainTextEdit):
|
|||||||
Start ``Backend`` process and render Pyte output as text.
|
Start ``Backend`` process and render Pyte output as text.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
prompt = pyqtSignal(bool)
|
||||||
|
|
||||||
def __init__(self, parent, cols=125, rows=50, **kwargs):
|
def __init__(self, parent, cols=125, rows=50, **kwargs):
|
||||||
|
# regexp to match prompt
|
||||||
|
self._prompt_re = None
|
||||||
|
# last prompt
|
||||||
|
self._prompt_str = None
|
||||||
# file descriptor to communicate with the subprocess
|
# file descriptor to communicate with the subprocess
|
||||||
self.fd = None
|
self.fd = None
|
||||||
self.backend = None
|
self.backend = None
|
||||||
@ -540,11 +575,13 @@ class _TerminalWidget(QtWidgets.QPlainTextEdit):
|
|||||||
elif code is not None:
|
elif code is not None:
|
||||||
self.write(code)
|
self.write(code)
|
||||||
|
|
||||||
def push(self, text):
|
def push(self, text, hit_return=False):
|
||||||
"""
|
"""
|
||||||
Write 'text' to terminal
|
Write 'text' to terminal
|
||||||
"""
|
"""
|
||||||
self.write(text.encode("utf-8"))
|
self.write(text.encode("utf-8"))
|
||||||
|
if hit_return:
|
||||||
|
self.write(b"\n")
|
||||||
|
|
||||||
def contextMenuEvent(self, event):
|
def contextMenuEvent(self, event):
|
||||||
if self.fd is None:
|
if self.fd is None:
|
||||||
@ -650,6 +687,20 @@ class _TerminalWidget(QtWidgets.QPlainTextEdit):
|
|||||||
self.output[line_no] = line
|
self.output[line_no] = line
|
||||||
# fill the text area with HTML contents in one go
|
# fill the text area with HTML contents in one go
|
||||||
self.appendHtml(f"<pre>{chr(10).join(self.output)}</pre>")
|
self.appendHtml(f"<pre>{chr(10).join(self.output)}</pre>")
|
||||||
|
|
||||||
|
if self._prompt_re is not None:
|
||||||
|
text_buf = self.toPlainText()
|
||||||
|
prompt = self._prompt_re.search(text_buf)
|
||||||
|
if prompt is None:
|
||||||
|
if self._prompt_str:
|
||||||
|
self.prompt.emit(False)
|
||||||
|
self._prompt_str = None
|
||||||
|
else:
|
||||||
|
prompt_str = prompt.string.rstrip()
|
||||||
|
if prompt_str != self._prompt_str:
|
||||||
|
self._prompt_str = prompt_str
|
||||||
|
self.prompt.emit(True)
|
||||||
|
|
||||||
# did updates, all clean
|
# did updates, all clean
|
||||||
screen.dirty.clear()
|
screen.dirty.clear()
|
||||||
|
|
||||||
@ -728,6 +779,24 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
console = BECConsole(mainwin)
|
console = BECConsole(mainwin)
|
||||||
mainwin.setCentralWidget(console)
|
mainwin.setCentralWidget(console)
|
||||||
|
|
||||||
|
def check_prompt(at_prompt):
|
||||||
|
if at_prompt:
|
||||||
|
print("NEW PROMPT")
|
||||||
|
else:
|
||||||
|
print("EXECUTING SOMETHING...")
|
||||||
|
|
||||||
|
console.set_prompt_tokens(
|
||||||
|
(Token.OutPromptNum, "•"),
|
||||||
|
(Token.Prompt, ""), # will match arbitrary string,
|
||||||
|
(Token.Prompt, " ["),
|
||||||
|
(Token.PromptNum, "3"),
|
||||||
|
(Token.Prompt, "/"),
|
||||||
|
(Token.PromptNum, "1"),
|
||||||
|
(Token.Prompt, "] "),
|
||||||
|
(Token.Prompt, "❯❯"),
|
||||||
|
)
|
||||||
|
console.prompt.connect(check_prompt)
|
||||||
console.start()
|
console.start()
|
||||||
|
|
||||||
# Show widget and launch Qt's event loop.
|
# Show widget and launch Qt's event loop.
|
||||||
|
Reference in New Issue
Block a user