class DebugWindow(QMainWindow): """A main window to edit text and examine the generated token structure. Example:: from PyQt5.Qt import * a=QApplication([]) from parceqt.debug import DebugWindow w = DebugWindow() w.resize(1200,900) w.show() w.set_theme("default", True) from parce.lang.css import * w.set_root_lexicon(Css.root) w.set_text(open("path/to/parce/themes/default.css").read()) In the debug window you can edit the text at the left and directly at the right examine the tree structure. Along the top of the window the path to the token at the current cursor position is displayed, from the root lexicon upto the token, from which the action is displayed. Clicking a button selects the associated range of the context or token in the text view. Clicking an item in the tree also selects that range in the text. Moving the cursor in the text updates the current item in the tree, and the displayed ancestor path. """ show_updated_region_enabled = False def __init__(self, parent=None): super().__init__(parent, windowTitle="parceqt debugger") f = self._updated_format = QTextCharFormat() c = QColor("palegreen") c.setAlpha(64) f.setBackground(c) f = self._currentline_format = QTextCharFormat() f.setProperty(QTextCharFormat.FullWidthSelection, True) self._actions = Actions(self) self._actions.add_menus(self.menuBar()) widget = QWidget(self) self.setCentralWidget(widget) layout = QVBoxLayout(margin=4, spacing=2) widget.setLayout(layout) top_layout = QHBoxLayout(margin=0, spacing=0) self.guessButton = QToolButton(self, clicked=self.guess_root_lexicon, toolTip="Guess Language", icon=self.style().standardIcon( QStyle.SP_BrowserReload)) self.lexiconChooser = LexiconChooser(self) self.ancestorView = AncestorView(self) top_layout.addWidget(self.guessButton) top_layout.addWidget(self.lexiconChooser) top_layout.addWidget(self.ancestorView) top_layout.addStretch(10) layout.addLayout(top_layout) self.guessButton.setFixedHeight( self.lexiconChooser.sizeHint().height()) splitter = QSplitter(self, orientation=Qt.Horizontal) layout.addWidget(splitter, 100) self.textEdit = QPlainTextEdit(lineWrapMode=QPlainTextEdit.NoWrap, cursorWidth=2) self.treeView = QTreeView() splitter.addWidget(self.textEdit) splitter.addWidget(self.treeView) splitter.setStretchFactor(0, 3) splitter.setStretchFactor(1, 2) self.extraSelectionManager = ExtraSelectionManager(self.textEdit) self.document = d = self.textEdit.document() self.textEdit.setDocument(self.document) self.worker = w = parceqt.worker(d) self.builder = b = w.builder() w.debugging = True self.setStatusBar(QStatusBar()) self.create_model() # signal connections self.textEdit.viewport().installEventFilter(self) self.textEdit.installEventFilter(self) self.lexiconChooser.lexicon_changed.connect( self.slot_root_lexicon_changed) self.ancestorView.node_clicked.connect(self.slot_node_clicked) w.started.connect(self.slot_build_started) w.tree_updated.connect(self.slot_build_updated) self.textEdit.cursorPositionChanged.connect( self.slot_cursor_position_changed) self.treeView.clicked.connect(self.slot_item_clicked) self.textEdit.setFocus() self.set_theme() # somewhat larger font by default font = self.textEdit.font() font.setPointSizeF(11) self.textEdit.setFont(font) def create_model(self): """Instantiate a tree model for the tree view.""" m = self.treeView.model() if not m: m = parceqt.treemodel.TreeModel(self.builder.root) m.connect_debugging_builder(self.builder) self.treeView.setModel(m) def delete_model(self): """Delete the model and remove it from the tree.""" m = self.treeView.model() if m: m.disconnect_debugging_builder(self.builder) self.treeView.setModel(None) m.deleteLater() def set_text(self, text): """Set the text in the text edit.""" self.document.setPlainText(text) def set_root_lexicon(self, lexicon): """Set the root lexicon to use.""" self.lexiconChooser.set_root_lexicon(lexicon) def guess_root_lexicon(self): """Again choose the root lexicon based on the text.""" text = self.document.toPlainText() if text: self.set_root_lexicon(parce.find(contents=text)) def open_file(self, filename): """Read a file from disk and guess the language.""" text = read_file(filename) root_lexicon = parce.find(filename=filename, contents=text) self.set_text(text) self.set_root_lexicon(root_lexicon) c = self.textEdit.textCursor() c.setPosition(0) self.textEdit.setTextCursor(c) def set_theme(self, theme="default", adjust_widget=True): """Set the theme to use for the text edit.""" if isinstance(theme, str): theme = parce.theme_by_name(theme) formatter = parceqt.formatter.Formatter(theme) if theme else None if adjust_widget: if formatter: font = formatter.font(self) self.textEdit.setPalette(formatter.palette(self)) else: font = QApplication.font(self) self.textEdit.setPalette(QApplication.palette(self)) font.setPointSizeF(self.textEdit.font().pointSizeF()) # keep size self.textEdit.setFont(font) self.highlight_current_line() h = parceqt.highlighter.SyntaxHighlighter.instance(self.worker) h.set_formatter(formatter) def slot_build_started(self): """Called when the tree builder has started a build.""" self.treeView.setCursor(Qt.BusyCursor) def slot_build_updated(self): """Called when the tree builder has finished a build.""" self.treeView.unsetCursor() self.slot_cursor_position_changed() self.statusBar().showMessage(", ".join( lexicon_names(self.builder.lexicons))) tree = self.worker.get_root() self.lexiconChooser.setToolTip( parceqt.treemodel.TreeModel.node_tooltip(tree)) if self.show_updated_region_enabled: self.show_updated_region() def slot_cursor_position_changed(self): """Called when the text cursor moved.""" tree = self.worker.get_root() if tree: pos = self.textEdit.textCursor().position() doc = parceqt.document.Document(self.document) token = doc.token(pos) if token: self.ancestorView.set_token_path(token) model = self.treeView.model() if model: index = model.get_model_index(token) self.treeView.setCurrentIndex(index) elif tree is not None: self.ancestorView.clear() self.highlight_current_line() def slot_item_clicked(self, index): """Called when a node in the tree view is clicked.""" tree = self.worker.get_root() if tree: model = self.treeView.model() if model: node = self.treeView.model().get_node(index) cursor = self.textEdit.textCursor() cursor.setPosition(node.end) cursor.setPosition(node.pos, QTextCursor.KeepAnchor) self.textEdit.setTextCursor(cursor) self.textEdit.setFocus() def slot_node_clicked(self, node): """Called when a button in the ancestor view is clicked.""" tree = self.worker.get_root() if tree and node.root() is tree: cursor = self.textEdit.textCursor() cursor.setPosition(node.end) cursor.setPosition(node.pos, QTextCursor.KeepAnchor) self.textEdit.setTextCursor(cursor) self.textEdit.setFocus() model = self.treeView.model() if model: index = model.get_model_index(node) self.treeView.expand(index) self.treeView.setCurrentIndex(index) def slot_root_lexicon_changed(self, lexicon): """Called when the root lexicon is changed.""" parceqt.set_root_lexicon(self.document, lexicon) def highlight_current_line(self): """Highlight the current line.""" group = QPalette.Active if self.textEdit.hasFocus( ) else QPalette.Inactive p = self.textEdit.palette() color = p.color(group, QPalette.AlternateBase) self._currentline_format.setBackground(color) if color != p.color(group, QPalette.Base): c = self.textEdit.textCursor() c.clearSelection() self.extraSelectionManager.highlight(self._currentline_format, [c]) else: self.extraSelectionManager.clear(self._currentline_format) def show_updated_region(self): """Highlight the updated region for 2 seconds.""" end = self.builder.end if end >= self.document.characterCount() - 1: end = self.document.characterCount() - 1 if self.builder.start == 0: return c = QTextCursor(self.document) c.setPosition(end) c.setPosition(self.builder.start, QTextCursor.KeepAnchor) self.extraSelectionManager.highlight(self._updated_format, [c], msec=2000) def clear_updated_region(self): self.extraSelectionManager.clear(self._updated_format) def eventFilter(self, obj, ev): """Implemented to support Ctrl+wheel zooming and keybfocus handling.""" if obj == self.textEdit: if ev.type() in (QEvent.FocusIn, QEvent.FocusOut): self.highlight_current_line() else: # viewport if ev.type() == QEvent.Wheel and ev.modifiers( ) == Qt.ControlModifier: if ev.angleDelta().y() > 0: self.textEdit.zoomIn() elif ev.angleDelta().y() < 0: self.textEdit.zoomOut() return True return False