spiceditor 0.0.4__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.
Potentially problematic release.
This version of spiceditor might be problematic. Click here for more details.
- pyspice/__init__.py +0 -0
- pyspice/dialogs.py +103 -0
- pyspice/editor_widget.py +196 -0
- pyspice/file_browser.py +143 -0
- pyspice/highlighter.py +74 -0
- pyspice/line_number_text_edit.py +54 -0
- pyspice/magic_scrollbar.py +11 -0
- pyspice/main.py +435 -0
- pyspice/resources.py +1120 -0
- pyspice/spice_console.py +282 -0
- pyspice/spice_magic_editor.py +389 -0
- pyspice/splitter.py +118 -0
- pyspice/term.py +63 -0
- pyspice/textract.py +597 -0
- pyspice/utils.py +33 -0
- spiceditor/__init__.py +0 -0
- spiceditor/dialogs.py +103 -0
- spiceditor/editor_widget.py +196 -0
- spiceditor/file_browser.py +143 -0
- spiceditor/highlighter.py +74 -0
- spiceditor/line_number_text_edit.py +54 -0
- spiceditor/magic_scrollbar.py +11 -0
- spiceditor/main.py +435 -0
- spiceditor/resources.py +1120 -0
- spiceditor/spice_console.py +282 -0
- spiceditor/spice_magic_editor.py +389 -0
- spiceditor/splitter.py +118 -0
- spiceditor/term.py +63 -0
- spiceditor/textract.py +597 -0
- spiceditor/utils.py +33 -0
- spiceditor-0.0.4.dist-info/LICENSE +674 -0
- spiceditor-0.0.4.dist-info/METADATA +31 -0
- spiceditor-0.0.4.dist-info/RECORD +51 -0
- spiceditor-0.0.4.dist-info/WHEEL +5 -0
- spiceditor-0.0.4.dist-info/entry_points.txt +2 -0
- spiceditor-0.0.4.dist-info/top_level.txt +1 -0
- spyce/__init__.py +0 -0
- spyce/dialogs.py +103 -0
- spyce/editor_widget.py +196 -0
- spyce/file_browser.py +143 -0
- spyce/highlighter.py +74 -0
- spyce/line_number_text_edit.py +54 -0
- spyce/magic_scrollbar.py +11 -0
- spyce/main.py +435 -0
- spyce/resources.py +1120 -0
- spyce/spice_console.py +282 -0
- spyce/spice_magic_editor.py +389 -0
- spyce/splitter.py +118 -0
- spyce/term.py +63 -0
- spyce/textract.py +597 -0
- spyce/utils.py +33 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
3
|
+
from PyQt5.QtCore import QTimer, pyqtSignal
|
|
4
|
+
from PyQt5.QtGui import QFont
|
|
5
|
+
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QApplication
|
|
6
|
+
from easyconfig2.easyconfig import EasyConfig2
|
|
7
|
+
from qtconsole.manager import QtKernelManager
|
|
8
|
+
from qtconsole.rich_jupyter_widget import RichJupyterWidget
|
|
9
|
+
from termqt import Terminal
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# from qtpyTerminal import qtpyTerminal
|
|
13
|
+
|
|
14
|
+
class SpiceConsole(QWidget):
|
|
15
|
+
done = pyqtSignal()
|
|
16
|
+
|
|
17
|
+
def __init__(self, config):
|
|
18
|
+
super().__init__()
|
|
19
|
+
self.keep_code = False
|
|
20
|
+
self.config = config
|
|
21
|
+
|
|
22
|
+
def execute(self, code, clear=True):
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
def clear(self):
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
def set_dark_mode(self, value):
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
def set_font_size(self, size):
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
def set_config(self, config):
|
|
35
|
+
self.config = config
|
|
36
|
+
|
|
37
|
+
def config_read(self):
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
def set_keep_code(self, value):
|
|
41
|
+
self.keep_code = value
|
|
42
|
+
|
|
43
|
+
def get_file_extension(self):
|
|
44
|
+
return ".py"
|
|
45
|
+
|
|
46
|
+
def update_config(self, **kwargs):
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class JupyterConsole(SpiceConsole):
|
|
51
|
+
|
|
52
|
+
def __init__(self, config):
|
|
53
|
+
super().__init__(config)
|
|
54
|
+
|
|
55
|
+
kernel_manager = QtKernelManager(kernel_name='python3')
|
|
56
|
+
kernel_manager.start_kernel()
|
|
57
|
+
kernel_client = kernel_manager.client()
|
|
58
|
+
kernel_client.start_channels()
|
|
59
|
+
|
|
60
|
+
self.jupyter_widget = RichJupyterWidget()
|
|
61
|
+
font = QFont("Monospace")
|
|
62
|
+
font.setStyleHint(QFont.TypeWriter)
|
|
63
|
+
font.setPixelSize(18)
|
|
64
|
+
self.jupyter_widget.font = font
|
|
65
|
+
|
|
66
|
+
# self.jupyter_widget._set_font()
|
|
67
|
+
self.jupyter_widget.kernel_manager = kernel_manager
|
|
68
|
+
self.jupyter_widget.kernel_client = kernel_client
|
|
69
|
+
|
|
70
|
+
# Customize the prompt
|
|
71
|
+
self.jupyter_widget.include_other_output = False
|
|
72
|
+
self.jupyter_widget.banner = "" # Remove banner
|
|
73
|
+
self.jupyter_widget.input_prompt = "" # Remove input prompt
|
|
74
|
+
self.jupyter_widget.output_prompt = "" # Remove output prompt
|
|
75
|
+
self.jupyter_widget.set_default_style(colors='linux')
|
|
76
|
+
|
|
77
|
+
self.editor = self.jupyter_widget._control
|
|
78
|
+
|
|
79
|
+
layout = QVBoxLayout()
|
|
80
|
+
layout.addWidget(self.jupyter_widget)
|
|
81
|
+
self.setLayout(layout)
|
|
82
|
+
self.timer = QTimer()
|
|
83
|
+
self.timer.setSingleShot(True)
|
|
84
|
+
self.timer.timeout.connect(self.done.emit)
|
|
85
|
+
|
|
86
|
+
def config_read(self):
|
|
87
|
+
pass
|
|
88
|
+
|
|
89
|
+
def update_config(self, **kwargs):
|
|
90
|
+
path = self.config.progs_path.get()
|
|
91
|
+
if path:
|
|
92
|
+
self.jupyter_widget.execute("cd " + path, hidden=True)
|
|
93
|
+
|
|
94
|
+
size = self.config.font_size.get()
|
|
95
|
+
|
|
96
|
+
if size>=0:
|
|
97
|
+
self.set_font_size(size + 10)
|
|
98
|
+
|
|
99
|
+
def set_dark_mode(self, value):
|
|
100
|
+
if value:
|
|
101
|
+
self.jupyter_widget.set_default_style(colors='linux')
|
|
102
|
+
else:
|
|
103
|
+
self.jupyter_widget.set_default_style(colors='lightbg')
|
|
104
|
+
|
|
105
|
+
def execute(self, code, clear=True):
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
# self.jupyter_widget._control.setText("")
|
|
109
|
+
# def filtering():
|
|
110
|
+
# text = self.editor.toPlainText()
|
|
111
|
+
# if text.endswith(" ...: "):
|
|
112
|
+
# if clear:
|
|
113
|
+
# self.editor.clear()
|
|
114
|
+
#
|
|
115
|
+
# # pattern = r"In \[\d+\]:"
|
|
116
|
+
# # if re.search(pattern, text[-10:]):
|
|
117
|
+
# # self.timer.stop()
|
|
118
|
+
# # self.timer.start(250)
|
|
119
|
+
#
|
|
120
|
+
# self.editor.textChanged.connect(filtering)
|
|
121
|
+
# # self.jupyter_widget._control.setFocus()
|
|
122
|
+
# if "input" in code:
|
|
123
|
+
# self.jupyter_widget._control.setFocus()
|
|
124
|
+
# QApplication.processEvents()
|
|
125
|
+
|
|
126
|
+
def run():
|
|
127
|
+
if code.strip():
|
|
128
|
+
code_reset = code
|
|
129
|
+
self.jupyter_widget.execute(code_reset, interactive=True)
|
|
130
|
+
if clear:
|
|
131
|
+
self.jupyter_widget._control.clear()
|
|
132
|
+
|
|
133
|
+
clearer = '''
|
|
134
|
+
import sys
|
|
135
|
+
import os
|
|
136
|
+
import importlib
|
|
137
|
+
|
|
138
|
+
cwd = os.getcwd()
|
|
139
|
+
|
|
140
|
+
# Identify modules that are imported from the current directory
|
|
141
|
+
user_modules = [m for m in sys.modules if hasattr(
|
|
142
|
+
sys.modules[m], '__file__') and sys.modules[m].__file__ and sys.modules[m].__file__.startswith(cwd)]
|
|
143
|
+
|
|
144
|
+
for m in user_modules:
|
|
145
|
+
print("Reloading ", m, type(m))
|
|
146
|
+
importlib.reload(sys.modules[m])
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
'''
|
|
150
|
+
self.jupyter_widget.execute(clearer, hidden=True)
|
|
151
|
+
QTimer.singleShot(100, run)
|
|
152
|
+
|
|
153
|
+
def clear(self):
|
|
154
|
+
pass # self.jupyter_widget.execute("%clear")
|
|
155
|
+
|
|
156
|
+
def set_font_size(self, font_size):
|
|
157
|
+
font = QFont("Monospace")
|
|
158
|
+
font.setStyleHint(QFont.TypeWriter)
|
|
159
|
+
font.setPixelSize(font_size)
|
|
160
|
+
self.jupyter_widget._control.setFont(font)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
import platform
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class TermQtConsole(SpiceConsole):
|
|
167
|
+
def __init__(self, config):
|
|
168
|
+
super().__init__(config)
|
|
169
|
+
self.terminal = Terminal(400, 600, font_size=18)
|
|
170
|
+
layout = QVBoxLayout()
|
|
171
|
+
layout.addWidget(self.terminal)
|
|
172
|
+
self.setLayout(layout)
|
|
173
|
+
|
|
174
|
+
my_platform = platform.system()
|
|
175
|
+
|
|
176
|
+
if my_platform in ["Linux", "Darwin"]:
|
|
177
|
+
bin = "/bin/bash"
|
|
178
|
+
|
|
179
|
+
from termqt import TerminalPOSIXExecIO
|
|
180
|
+
terminal_io = TerminalPOSIXExecIO(
|
|
181
|
+
self.terminal.row_len,
|
|
182
|
+
self.terminal.col_len,
|
|
183
|
+
bin
|
|
184
|
+
)
|
|
185
|
+
elif my_platform == "Windows":
|
|
186
|
+
bin = "cmd"
|
|
187
|
+
|
|
188
|
+
from termqt import TerminalWinptyIO
|
|
189
|
+
terminal_io = TerminalWinptyIO(
|
|
190
|
+
self.terminal.row_len,
|
|
191
|
+
self.terminal.col_len,
|
|
192
|
+
bin
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
# it turned out that cmd prefers to handle resize by itself
|
|
196
|
+
# see https://github.com/TerryGeng/termqt/issues/7
|
|
197
|
+
auto_wrap_enabled = False
|
|
198
|
+
else:
|
|
199
|
+
sys.exit(-1)
|
|
200
|
+
|
|
201
|
+
self.terminal.enable_auto_wrap(True)
|
|
202
|
+
|
|
203
|
+
terminal_io.stdout_callback = self.terminal.stdout
|
|
204
|
+
self.terminal.stdin_callback = terminal_io.write
|
|
205
|
+
self.terminal.resize_callback = terminal_io.resize
|
|
206
|
+
terminal_io.spawn()
|
|
207
|
+
# self.terminal.input("python")
|
|
208
|
+
|
|
209
|
+
def set_config(self, config: EasyConfig2):
|
|
210
|
+
super().set_config(config)
|
|
211
|
+
terminal = config.root().addSubSection("Terminal")
|
|
212
|
+
self.init = terminal.addString("init", pretty="Init command (e.g. python)")
|
|
213
|
+
self.temp_file = terminal.addString("temp_file", pretty="Temp file name")
|
|
214
|
+
self.command = terminal.addString("command", pretty="Command")
|
|
215
|
+
self.file_extension = terminal.addCombobox("file_extension", pretty="File extension", items=[".py", ".pas"])
|
|
216
|
+
|
|
217
|
+
def config_read(self):
|
|
218
|
+
super().config_read()
|
|
219
|
+
init = self.init.get_value()
|
|
220
|
+
if init is not None:
|
|
221
|
+
init += "\n"
|
|
222
|
+
self.terminal.input(init.encode("utf-8"))
|
|
223
|
+
|
|
224
|
+
def execute(self, code, clear=True):
|
|
225
|
+
temp_file = self.temp_file.get_value()
|
|
226
|
+
if temp_file is not None and temp_file.strip():
|
|
227
|
+
with open(temp_file, "w") as f:
|
|
228
|
+
f.write(code)
|
|
229
|
+
else:
|
|
230
|
+
self.terminal.input(code.encode("utf-8"))
|
|
231
|
+
|
|
232
|
+
command = self.command.get_value()
|
|
233
|
+
if command is not None and command.strip():
|
|
234
|
+
command += "\r\n"
|
|
235
|
+
self.terminal.input(command.encode("utf-8"))
|
|
236
|
+
|
|
237
|
+
def clear(self):
|
|
238
|
+
self.terminal.input("clear\r\n".encode("utf-8"))
|
|
239
|
+
|
|
240
|
+
def get_file_extension(self):
|
|
241
|
+
return self.file_extension.get_item(self.file_extension.get(0))
|
|
242
|
+
|
|
243
|
+
def set_font_size(self, size):
|
|
244
|
+
try:
|
|
245
|
+
self.terminal.font_size = int(size * 0.75)
|
|
246
|
+
font = QFont("Monospace")
|
|
247
|
+
font.setStyleHint(QFont.Monospace)
|
|
248
|
+
font.setPointSize(size)
|
|
249
|
+
self.terminal.set_font(font)
|
|
250
|
+
# self.terminal.input("clear\r\n".encode("utf-8"))
|
|
251
|
+
except:
|
|
252
|
+
pass
|
|
253
|
+
|
|
254
|
+
def update_config(self):
|
|
255
|
+
font_size = self.config.root().get_child("font_size").get_value()
|
|
256
|
+
if font_size is not None:
|
|
257
|
+
self.set_font_size(font_size + 10)
|
|
258
|
+
|
|
259
|
+
def resizeEvent(self, a0):
|
|
260
|
+
# TODO: self.terminal.set_canvas_size(self.width(), self.height())
|
|
261
|
+
pass
|
|
262
|
+
|
|
263
|
+
# class Console2(SpiceTerminal):
|
|
264
|
+
# def __init__(self):
|
|
265
|
+
# super().__init__()
|
|
266
|
+
# self.terminal = qtpyTerminal()
|
|
267
|
+
# self.terminal.term.setFont(QFont("Monospace", 14))
|
|
268
|
+
# self.terminal.term.setMinimumWidth(200)
|
|
269
|
+
# self.terminal.setMinimumWidth(200)
|
|
270
|
+
#
|
|
271
|
+
# layout = QVBoxLayout()
|
|
272
|
+
# layout.addWidget(self.terminal)
|
|
273
|
+
# self.setLayout(layout)
|
|
274
|
+
# self.terminal.start()
|
|
275
|
+
#
|
|
276
|
+
# def execute(self, code, clear=True):
|
|
277
|
+
# with open("output.pas", "w") as f:
|
|
278
|
+
# f.write(code)
|
|
279
|
+
# self.terminal.push("fpc output.pas && ./output\n")
|
|
280
|
+
#
|
|
281
|
+
# def clear(self):
|
|
282
|
+
# self.terminal.push("clear\n")
|
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
import random
|
|
2
|
+
import re
|
|
3
|
+
|
|
4
|
+
import autopep8
|
|
5
|
+
from PyQt5 import QtGui
|
|
6
|
+
from PyQt5.QtCore import pyqtSignal, Qt, QTimer
|
|
7
|
+
from PyQt5.QtGui import QFont, QFontMetrics, QColor, QPainter, QTextCursor
|
|
8
|
+
from PyQt5.QtWidgets import QTextEdit, QHBoxLayout, QScrollBar, QApplication
|
|
9
|
+
|
|
10
|
+
from spiceditor.line_number_text_edit import LineNumberTextEdit
|
|
11
|
+
from spiceditor.magic_scrollbar import MagicScrollBar
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SpiceMagicEditor(QTextEdit):
|
|
15
|
+
ctrl_enter = pyqtSignal()
|
|
16
|
+
info = pyqtSignal(str, int, int)
|
|
17
|
+
|
|
18
|
+
def __init__(self, highlighter=None, font_size=18):
|
|
19
|
+
super().__init__()
|
|
20
|
+
self.highlighter = highlighter
|
|
21
|
+
self.suggestion = None
|
|
22
|
+
self.candidates = []
|
|
23
|
+
self.count = 0
|
|
24
|
+
self.mode = 0
|
|
25
|
+
self.code = ""
|
|
26
|
+
self.delay = 0.01
|
|
27
|
+
self.autocomplete_words = []
|
|
28
|
+
|
|
29
|
+
self.line_number_area = LineNumberTextEdit(self)
|
|
30
|
+
self.line_number_area.verticalScrollBar().valueChanged.connect(self.verticalScrollBar().setValue)
|
|
31
|
+
self.line_number_area.document().setDocumentMargin(0)
|
|
32
|
+
|
|
33
|
+
self.setContentsMargins(0, 0, 0, 0)
|
|
34
|
+
self.document().setDocumentMargin(0)
|
|
35
|
+
self.setViewportMargins(60, 0, 0, 0)
|
|
36
|
+
self.setLineWrapMode(QTextEdit.NoWrap)
|
|
37
|
+
self.setPlaceholderText("Write Python code here...")
|
|
38
|
+
|
|
39
|
+
self.setHorizontalScrollBar(MagicScrollBar())
|
|
40
|
+
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
|
|
41
|
+
|
|
42
|
+
self.textChanged.connect(self.text_changed)
|
|
43
|
+
self.verticalScrollBar().valueChanged.connect(self.line_number_area.verticalScrollBar().setValue)
|
|
44
|
+
self.horizontalScrollBar().rangeChanged.connect(self.text_changed)
|
|
45
|
+
|
|
46
|
+
self.cursorPositionChanged.connect(
|
|
47
|
+
lambda: self.line_number_area.highlight_line(self.textCursor().blockNumber()))
|
|
48
|
+
|
|
49
|
+
if self.highlighter:
|
|
50
|
+
self.highlighter.setDocument(self.document())
|
|
51
|
+
|
|
52
|
+
self.set_font_size(font_size)
|
|
53
|
+
|
|
54
|
+
def set_font_size(self, font_size):
|
|
55
|
+
font = QFont("Courier New")
|
|
56
|
+
# font.setStyleHint(QFont.TypeWriter)
|
|
57
|
+
font.setPixelSize(font_size)
|
|
58
|
+
self.setFont(font)
|
|
59
|
+
self.line_number_area.setFont(font)
|
|
60
|
+
three_numbers_width = QFontMetrics(font).width("000")
|
|
61
|
+
self.line_number_area.setFixedWidth(int(three_numbers_width + 12))
|
|
62
|
+
self.setViewportMargins(three_numbers_width + 15, 0, 0, 0)
|
|
63
|
+
|
|
64
|
+
def show_code(self):
|
|
65
|
+
self.show_all_code()
|
|
66
|
+
|
|
67
|
+
def set_dark_mode(self, dark):
|
|
68
|
+
self.line_number_area.set_dark_mode(dark)
|
|
69
|
+
self.highlighter.set_dark_mode(dark)
|
|
70
|
+
self.highlighter.setDocument(self.document())
|
|
71
|
+
|
|
72
|
+
def set_text(self, text):
|
|
73
|
+
self.setPlainText(text)
|
|
74
|
+
self.text_changed()
|
|
75
|
+
|
|
76
|
+
def text_changed(self):
|
|
77
|
+
text = self.toPlainText()
|
|
78
|
+
lines = text.split("\n")
|
|
79
|
+
v1 = self.line_number_area.verticalScrollBar().value()
|
|
80
|
+
self.line_number_area.blockSignals(True)
|
|
81
|
+
self.line_number_area.clear()
|
|
82
|
+
text = str()
|
|
83
|
+
for i in range(len(lines)):
|
|
84
|
+
text += "{:3d}\n".format(i + 1)
|
|
85
|
+
|
|
86
|
+
self.line_number_area.setPlainText(text)
|
|
87
|
+
self.line_number_area.setFixedHeight(self.height())
|
|
88
|
+
self.line_number_area.verticalScrollBar().setMaximum(self.verticalScrollBar().maximum())
|
|
89
|
+
self.line_number_area.verticalScrollBar().setValue(v1)
|
|
90
|
+
self.line_number_area.blockSignals(False)
|
|
91
|
+
|
|
92
|
+
def resizeEvent(self, a0) -> None:
|
|
93
|
+
self.text_changed()
|
|
94
|
+
self.blockSignals(True)
|
|
95
|
+
super().resizeEvent(a0)
|
|
96
|
+
self.blockSignals(False)
|
|
97
|
+
|
|
98
|
+
def format_code(self):
|
|
99
|
+
self.format_code()
|
|
100
|
+
|
|
101
|
+
def get_text(self):
|
|
102
|
+
return self.toPlainText()
|
|
103
|
+
|
|
104
|
+
def clear(self):
|
|
105
|
+
self.set_code("")
|
|
106
|
+
self.setPlainText("")
|
|
107
|
+
self.count = 0
|
|
108
|
+
self.set_mode(0)
|
|
109
|
+
|
|
110
|
+
def get_code(self):
|
|
111
|
+
return self.code
|
|
112
|
+
|
|
113
|
+
def get_remaining_chars(self):
|
|
114
|
+
diff = len(self.get_code()) - self.count
|
|
115
|
+
return diff
|
|
116
|
+
|
|
117
|
+
def set_delay(self, delay):
|
|
118
|
+
self.delay = delay
|
|
119
|
+
|
|
120
|
+
def append_autocomplete(self, words, clear=False):
|
|
121
|
+
if clear:
|
|
122
|
+
self.autocomplete_words.clear()
|
|
123
|
+
self.autocomplete_words += words if words else ""
|
|
124
|
+
|
|
125
|
+
def set_code(self, code):
|
|
126
|
+
self.setText("")
|
|
127
|
+
self.count = 0
|
|
128
|
+
self.code = code
|
|
129
|
+
self.set_mode(1)
|
|
130
|
+
self.setFocus()
|
|
131
|
+
|
|
132
|
+
def set_mode(self, mode):
|
|
133
|
+
self.mode = mode
|
|
134
|
+
self.setCursorWidth(3 if self.mode == 1 else 1)
|
|
135
|
+
# self.setReadOnly(self.mode == 1)
|
|
136
|
+
self.update()
|
|
137
|
+
|
|
138
|
+
def on_return_key(self, e):
|
|
139
|
+
return False
|
|
140
|
+
|
|
141
|
+
def complete_line(self, sleep=True):
|
|
142
|
+
self.info.emit(self.get_next_line(), self.get_remaining_chars(), 20)
|
|
143
|
+
if self.count < len(self.code):
|
|
144
|
+
# self.setText(self.toPlainText() + self.code[self.count])
|
|
145
|
+
self.insertPlainText(self.code[self.count])
|
|
146
|
+
self.moveCursor(QtGui.QTextCursor.End)
|
|
147
|
+
self.count += 1
|
|
148
|
+
|
|
149
|
+
if self.code[self.count - 1] == "\n":
|
|
150
|
+
# if next line is empty continue
|
|
151
|
+
# and show that line too
|
|
152
|
+
if len(self.get_rest_of_line()) > 0:
|
|
153
|
+
return True
|
|
154
|
+
|
|
155
|
+
QApplication.processEvents()
|
|
156
|
+
delay = int(self.delay) + random.randint(0, int(self.delay))
|
|
157
|
+
QTimer.singleShot(delay, self.complete_line)
|
|
158
|
+
|
|
159
|
+
def get_rest_of_line(self):
|
|
160
|
+
count = self.count
|
|
161
|
+
text = ""
|
|
162
|
+
while count < len(self.code):
|
|
163
|
+
text += self.code[count]
|
|
164
|
+
count += 1
|
|
165
|
+
if self.code[count - 1] == "\n":
|
|
166
|
+
return text[1:]
|
|
167
|
+
return ""
|
|
168
|
+
|
|
169
|
+
def get_spaces(self, line):
|
|
170
|
+
spaces = 0
|
|
171
|
+
for c in line:
|
|
172
|
+
if c == " ":
|
|
173
|
+
spaces += 1
|
|
174
|
+
else:
|
|
175
|
+
break
|
|
176
|
+
return spaces
|
|
177
|
+
|
|
178
|
+
def get_current_line(self):
|
|
179
|
+
cursor = self.textCursor()
|
|
180
|
+
cursor.movePosition(QtGui.QTextCursor.StartOfLine)
|
|
181
|
+
cursor.movePosition(QtGui.QTextCursor.EndOfLine, QtGui.QTextCursor.KeepAnchor)
|
|
182
|
+
current_line = cursor.selectedText()
|
|
183
|
+
return current_line
|
|
184
|
+
|
|
185
|
+
def append_next_char(self):
|
|
186
|
+
self.count += 1
|
|
187
|
+
self.setText(self.code[:self.count])
|
|
188
|
+
self.moveCursor(QtGui.QTextCursor.End)
|
|
189
|
+
|
|
190
|
+
def show_all_code(self):
|
|
191
|
+
self.setText(self.code)
|
|
192
|
+
self.set_mode(0)
|
|
193
|
+
self.moveCursor(QtGui.QTextCursor.End)
|
|
194
|
+
|
|
195
|
+
def get_current_line_text(self):
|
|
196
|
+
# Get the QTextCursor
|
|
197
|
+
cursor = self.textCursor()
|
|
198
|
+
|
|
199
|
+
# Move the cursor to the start and end of the current line
|
|
200
|
+
cursor.select(cursor.LineUnderCursor)
|
|
201
|
+
|
|
202
|
+
# Get the selected text
|
|
203
|
+
return cursor.selectedText()
|
|
204
|
+
|
|
205
|
+
def keyPressEvent(self, e: QtGui.QKeyEvent) -> None:
|
|
206
|
+
self.setFocusPolicy(Qt.StrongFocus)
|
|
207
|
+
|
|
208
|
+
if e.key() == Qt.Key_Escape:
|
|
209
|
+
self.set_mode(0 if self.mode == 1 else 0)
|
|
210
|
+
|
|
211
|
+
if self.mode == 1:
|
|
212
|
+
|
|
213
|
+
if e.key() == Qt.Key_Down:
|
|
214
|
+
self.show_all_code()
|
|
215
|
+
elif e.key() == Qt.Key_Control:
|
|
216
|
+
return
|
|
217
|
+
elif e.key() == Qt.Key_End:
|
|
218
|
+
while self.complete_line(False):
|
|
219
|
+
pass
|
|
220
|
+
self.set_mode(0)
|
|
221
|
+
elif e.key() == Qt.Key_Tab:
|
|
222
|
+
if e.modifiers() == Qt.ControlModifier:
|
|
223
|
+
while self.complete_line():
|
|
224
|
+
pass
|
|
225
|
+
self.set_mode(0)
|
|
226
|
+
else:
|
|
227
|
+
self.complete_line()
|
|
228
|
+
elif self.count < len(self.code):
|
|
229
|
+
self.info.emit(self.get_rest_of_line(), self.get_remaining_chars(), 1000)
|
|
230
|
+
self.append_next_char()
|
|
231
|
+
elif e.key() == Qt.Key_Return:
|
|
232
|
+
self.set_mode(0)
|
|
233
|
+
super().keyPressEvent(e)
|
|
234
|
+
elif e.key() == Qt.Key_Backspace:
|
|
235
|
+
self.set_mode(0)
|
|
236
|
+
else:
|
|
237
|
+
self.setText(self.toPlainText() + "\n")
|
|
238
|
+
self.moveCursor(QtGui.QTextCursor.End)
|
|
239
|
+
|
|
240
|
+
elif self.mode == 0:
|
|
241
|
+
|
|
242
|
+
if e.key() == Qt.Key_Tab:
|
|
243
|
+
self.tab_pressed()
|
|
244
|
+
elif e.key() == Qt.Key_Backspace:
|
|
245
|
+
self.suggestion = None
|
|
246
|
+
if self.get_current_line_text().endswith(" "):
|
|
247
|
+
for i in range(4):
|
|
248
|
+
self.textCursor().deletePreviousChar()
|
|
249
|
+
else:
|
|
250
|
+
super().keyPressEvent(e)
|
|
251
|
+
elif e.key() == Qt.Key_Return:
|
|
252
|
+
self.suggestion = None
|
|
253
|
+
if e.modifiers() == Qt.ControlModifier:
|
|
254
|
+
self.ctrl_enter.emit()
|
|
255
|
+
elif self.on_return_key(e):
|
|
256
|
+
pass
|
|
257
|
+
else:
|
|
258
|
+
super().keyPressEvent(e)
|
|
259
|
+
else:
|
|
260
|
+
self.suggestion = None
|
|
261
|
+
super().keyPressEvent(e)
|
|
262
|
+
self.cursorPositionChanged.emit()
|
|
263
|
+
|
|
264
|
+
def indent_selected(self):
|
|
265
|
+
cursor = self.textCursor()
|
|
266
|
+
|
|
267
|
+
if not cursor.hasSelection():
|
|
268
|
+
return # No selection, do nothing
|
|
269
|
+
|
|
270
|
+
# Get selected text (preserve newlines)
|
|
271
|
+
selected_text = cursor.selection().toPlainText()
|
|
272
|
+
|
|
273
|
+
# Add 4 spaces to each line
|
|
274
|
+
indented_text = "\n".join(" " + line for line in selected_text.splitlines())
|
|
275
|
+
|
|
276
|
+
# Replace selected text with indented version
|
|
277
|
+
cursor.beginEditBlock() # Start undo-able action
|
|
278
|
+
cursor.insertText(indented_text)
|
|
279
|
+
cursor.endEditBlock() # End undo-able action
|
|
280
|
+
|
|
281
|
+
def get_text_before_cursor(self):
|
|
282
|
+
cursor = self.textCursor()
|
|
283
|
+
cursor.movePosition(QTextCursor.StartOfBlock, QTextCursor.KeepAnchor) # Selecciona desde el inicio de la línea
|
|
284
|
+
return cursor.selectedText()
|
|
285
|
+
|
|
286
|
+
def tab_pressed(self):
|
|
287
|
+
|
|
288
|
+
if self.textCursor().hasSelection():
|
|
289
|
+
self.indent_selected()
|
|
290
|
+
return
|
|
291
|
+
|
|
292
|
+
# Let see if we have some autocomplete candidates
|
|
293
|
+
if self.suggestion is None:
|
|
294
|
+
current_words = re.split(r'\W+', self.toPlainText())
|
|
295
|
+
text_before_cursor = self.get_text_before_cursor()
|
|
296
|
+
words_before_cursos = re.split(r"[+\-*/= ]", text_before_cursor)
|
|
297
|
+
self.candidates = []
|
|
298
|
+
if words_before_cursos[-1] != "":
|
|
299
|
+
word_set = list(set(current_words + self.highlighter.get_keywords() + self.autocomplete_words))
|
|
300
|
+
self.candidates = [word for word in word_set if word.startswith(words_before_cursos[-1])]
|
|
301
|
+
if words_before_cursos[-1] in self.candidates:
|
|
302
|
+
self.candidates.remove(words_before_cursos[-1])
|
|
303
|
+
self.candidates.append(words_before_cursos[-1])
|
|
304
|
+
self.suggestion = words_before_cursos[-1]
|
|
305
|
+
|
|
306
|
+
if len(self.candidates) > 1:
|
|
307
|
+
# Remove the current suggestion
|
|
308
|
+
for _ in range(len(self.suggestion)):
|
|
309
|
+
self.textCursor().deletePreviousChar()
|
|
310
|
+
self.candidates.append(self.suggestion)
|
|
311
|
+
self.suggestion = self.candidates.pop(0)
|
|
312
|
+
self.insertPlainText(self.suggestion)
|
|
313
|
+
else:
|
|
314
|
+
self.insertPlainText(" ")
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
# if len(self.candidates) > 0:
|
|
319
|
+
|
|
320
|
+
# current_line = self.get_current_line_text()
|
|
321
|
+
#
|
|
322
|
+
# # it was just a tab
|
|
323
|
+
# if len(current_line) == 0 or current_line.endswith(" "):
|
|
324
|
+
# self.insertPlainText(" ")
|
|
325
|
+
# self.moveCursor(QtGui.QTextCursor.End)
|
|
326
|
+
# self.suggestion = None
|
|
327
|
+
# return
|
|
328
|
+
#
|
|
329
|
+
# # it was just a tab
|
|
330
|
+
# if len(current_line) > 0 and current_line[-1] in " (:)":
|
|
331
|
+
# self.suggestion = None
|
|
332
|
+
# return
|
|
333
|
+
#
|
|
334
|
+
# if self.suggestion is None:
|
|
335
|
+
#
|
|
336
|
+
# if len(self.candidates) == 0:
|
|
337
|
+
# self.insertPlainText(" ")
|
|
338
|
+
# return
|
|
339
|
+
# elif len(self.candidates) == 1:
|
|
340
|
+
# self.insertPlainText(self.candidates[0][len(unfinished_word):])
|
|
341
|
+
# self.moveCursor(QtGui.QTextCursor.End)
|
|
342
|
+
# return
|
|
343
|
+
# else:
|
|
344
|
+
# self.suggestion = unfinished_word
|
|
345
|
+
#
|
|
346
|
+
# for _ in range(len(self.suggestion)):
|
|
347
|
+
# self.textCursor().deletePreviousChar()
|
|
348
|
+
# self.candidates.append(self.suggestion)
|
|
349
|
+
# self.suggestion = self.candidates.pop(0)
|
|
350
|
+
# self.insertPlainText(self.suggestion)
|
|
351
|
+
|
|
352
|
+
def get_next_line(self):
|
|
353
|
+
count = self.count
|
|
354
|
+
remaining = self.code[count:]
|
|
355
|
+
remaining = remaining.split("\n")
|
|
356
|
+
remaining = remaining[1:]
|
|
357
|
+
remaining = [x for x in remaining if x.strip()]
|
|
358
|
+
if remaining:
|
|
359
|
+
return remaining[0]
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
class PascalEditor(SpiceMagicEditor):
|
|
363
|
+
pass
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
class PythonEditor(SpiceMagicEditor):
|
|
367
|
+
def format_code(self):
|
|
368
|
+
code = self.toPlainText()
|
|
369
|
+
if not code.endswith("\n"):
|
|
370
|
+
code += "\n"
|
|
371
|
+
self.setPlainText(autopep8.fix_code(code))
|
|
372
|
+
self.moveCursor(QtGui.QTextCursor.End)
|
|
373
|
+
|
|
374
|
+
def on_return_key(self, e):
|
|
375
|
+
current_line = self.get_current_line()
|
|
376
|
+
spaces = self.get_spaces(current_line)
|
|
377
|
+
if current_line.endswith(":"):
|
|
378
|
+
if self.textCursor().positionInBlock() == len(current_line):
|
|
379
|
+
self.insertPlainText("\n" + " " * (spaces + 4))
|
|
380
|
+
return True
|
|
381
|
+
return False
|
|
382
|
+
|
|
383
|
+
elif current_line.startswith(" "):
|
|
384
|
+
if current_line.strip():
|
|
385
|
+
self.insertPlainText("\n" + " " * spaces)
|
|
386
|
+
else:
|
|
387
|
+
self.insertPlainText("\n" + " " * (max(spaces - 4, 0)))
|
|
388
|
+
return True
|
|
389
|
+
return False
|