# Buggy code
def _on_key_pressed(self, event):
    if event.isAccepted():
        return

    with QMutexLocker(self.event_lock):
        key = event.key()
        text = to_text_string(event.text())

        if self.is_snippet_active:
            line, column = self.editor.get_cursor_line_column()
            node, snippet, text_node = self._find_node_by_position(line, column)
            if key == Qt.Key_Tab:
                event.accept()
                next_snippet = (self.active_snippet + 1) % len(self.snippets_map)
                self.select_snippet(next_snippet)
            elif key == Qt.Key_Escape:
                self.reset()
                event.accept()
            elif len(text) > 0:
                if node is not None:
                    if snippet is None:
                        # Constant text identifier was modified
                        self.reset()
                    else:
                        self._process_text(text)


# Traceback (AttributeError)
File "/home/dbau/miniconda3/envs/spyder-beta/lib/python3.7/site-packages/spyder/plugins/editor/extensions/snippets.py", line 194, in _on_key_pressed
self._process_text(text)
File "/home/dbau/miniconda3/envs/spyder-beta/lib/python3.7/site-packages/spyder/plugins/editor/extensions/snippets.py", line 49, in wrapper
return f(self, *args, **kwargs)
File "/home/dbau/miniconda3/envs/spyder-beta/lib/python3.7/site-packages/spyder/plugins/editor/extensions/snippets.py", line 204, in _process_text
self.delete_text(line, column)
File "/home/dbau/miniconda3/envs/spyder-beta/lib/python3.7/site-packages/spyder/plugins/editor/extensions/snippets.py", line 240, in delete_text
self._delete_token(delete_token, text_parent, line, column)
File "/home/dbau/miniconda3/envs/spyder-beta/lib/python3.7/site-packages/spyder/plugins/editor/extensions/snippets.py", line 263, in _delete_token
if left_node.name in MERGE_ALLOWED:
AttributeError: 'PlaceholderNode' object has no attribute 'name'

# Fixed code
def _on_key_pressed(self, event):
    if event.isAccepted():
        return

    with QMutexLocker(self.event_lock):
        key = event.key()
        text = to_text_string(event.text())

        if self.is_snippet_active:
            line, column = self.editor.get_cursor_line_column()
            node, snippet, text_node = self._find_node_by_position(line, column)
            if key == Qt.Key_Tab:
                event.accept()
                next_snippet = (self.active_snippet + 1) % len(self.snippets_map)
                self.select_snippet(next_snippet)

                if next_snippet == 0:
                    self.reset()
            elif key == Qt.Key_Escape:
                self.reset()
                event.accept()
            elif len(text) > 0:
                not_brace = text not in {"(", ")", "[", "]", "{", "}"}
                not_completion = text not in self.editor.auto_completion_characters
                not_signature = text not in self.editor.signature_completion_characters
                valid = not_brace and not_completion and not_signature
                if node is not None:
                    if snippet is None or text == "\n":
                        # Constant text identifier was modified
                        self.reset()
                    elif valid or text == "\b":
                        self._process_text(text)