11
11
# pylint: disable-msg=R0911
12
12
# pylint: disable-msg=R0201
14
#TODO: Add a button "Opened files management" -> opens a qlistwidget with
15
# checkboxes + toolbar with "Save", "Close"
17
14
from PyQt4.QtGui import (QVBoxLayout, QFileDialog, QMessageBox, QMenu, QFont,
18
QAction, QApplication, QWidget, QHBoxLayout, QSplitter,
19
QComboBox, QKeySequence, QShortcut, QSizePolicy,
15
QAction, QApplication, QWidget, QHBoxLayout, QLabel,
16
QKeySequence, QShortcut, QMainWindow, QSplitter,
17
QListWidget, QListWidgetItem, QDialog, QLineEdit,
18
QIntValidator, QDialogButtonBox, QGridLayout)
21
19
from PyQt4.QtCore import (SIGNAL, Qt, QFileInfo, QThread, QObject, QByteArray,
22
PYQT_VERSION_STR, QSize, QPoint)
20
PYQT_VERSION_STR, QSize, QPoint, SLOT, QTimer)
25
23
import os.path as osp
32
from spyderlib.utils import encoding, sourcecode
30
from spyderlib.utils import encoding, sourcecode, programs
33
31
from spyderlib.config import get_icon, get_font
34
32
from spyderlib.utils.qthelpers import (create_action, add_actions, mimedata2url,
35
33
get_filetype_icon, translate,
37
35
from spyderlib.widgets.tabs import BaseTabs
38
36
from spyderlib.widgets.findreplace import FindReplace
39
from spyderlib.widgets.editortools import check, ClassBrowser
41
from spyderlib.widgets.qscieditor.qscieditor import QsciEditor as CodeEditor
42
from spyderlib.widgets.qscieditor.qscieditor import Printer #@UnusedImport
43
from spyderlib.widgets.qscieditor.qscibase import TextEditBaseWidget #@UnusedImport
45
from spyderlib.widgets.qteditor.qteditor import QtEditor as CodeEditor
46
from spyderlib.widgets.qteditor.qteditor import Printer #@UnusedImport
47
from spyderlib.widgets.qteditor.qtebase import TextEditBaseWidget #@UnusedImport
37
from spyderlib.widgets.editortools import OutlineExplorer, check
38
from spyderlib.widgets.codeeditor import syntaxhighlighters
39
from spyderlib.widgets.codeeditor.base import TextEditBaseWidget #@UnusedImport
40
from spyderlib.widgets.codeeditor.codeeditor import CodeEditor, get_primary_at
41
from spyderlib.widgets.codeeditor.codeeditor import Printer #@UnusedImport
44
class GoToLineDialog(QDialog):
45
def __init__(self, editor):
46
QDialog.__init__(self, editor)
49
self.setWindowTitle(translate("Editor", "Editor"))
52
label = QLabel(translate("Editor", "Go to line:"))
53
self.lineedit = QLineEdit()
54
validator = QIntValidator(self.lineedit)
55
validator.setRange(1, editor.get_line_count())
56
self.lineedit.setValidator(validator)
57
cl_label = QLabel(translate("Editor", "Current line:"))
58
cl_label_v = QLabel("<b>%d</b>" % editor.get_cursor_line_number())
59
last_label = QLabel(translate("Editor", "Line count:"))
60
last_label_v = QLabel("%d" % editor.get_line_count())
62
glayout = QGridLayout()
63
glayout.addWidget(label, 0, 0, Qt.AlignVCenter|Qt.AlignRight)
64
glayout.addWidget(self.lineedit, 0, 1, Qt.AlignVCenter)
65
glayout.addWidget(cl_label, 1, 0, Qt.AlignVCenter|Qt.AlignRight)
66
glayout.addWidget(cl_label_v, 1, 1, Qt.AlignVCenter)
67
glayout.addWidget(last_label, 2, 0, Qt.AlignVCenter|Qt.AlignRight)
68
glayout.addWidget(last_label_v, 2, 1, Qt.AlignVCenter)
70
bbox = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel,
72
self.connect(bbox, SIGNAL("accepted()"), SLOT("accept()"))
73
self.connect(bbox, SIGNAL("rejected()"), SLOT("reject()"))
74
btnlayout = QVBoxLayout()
75
btnlayout.addWidget(bbox)
76
btnlayout.addStretch(1)
78
ok_button = bbox.button(QDialogButtonBox.Ok)
79
ok_button.setEnabled(False)
80
self.connect(self.lineedit, SIGNAL("textChanged(QString)"),
81
lambda text: ok_button.setEnabled(len(text) > 0))
83
layout = QHBoxLayout()
84
layout.addLayout(glayout)
85
layout.addLayout(btnlayout)
86
self.setLayout(layout)
88
self.lineedit.setFocus()
90
def get_line_number(self):
91
return int(self.lineedit.text())
94
class FileListDialog(QDialog):
95
def __init__(self, parent, tabs, fullpath_sorting):
96
QDialog.__init__(self, parent)
100
self.setWindowIcon(get_icon('filelist.png'))
101
self.setWindowTitle(translate("Editor", "File list management"))
105
flabel = QLabel(translate("Editor", "Filter:"))
106
self.edit = QLineEdit(self)
107
self.connect(self.edit, SIGNAL("returnPressed()"), self.edit_file)
108
self.connect(self.edit, SIGNAL("textChanged(QString)"),
109
lambda text: self.synchronize(0))
110
fhint = QLabel(translate("Editor", "(press <b>Enter</b> to edit file)"))
111
edit_layout = QHBoxLayout()
112
edit_layout.addWidget(flabel)
113
edit_layout.addWidget(self.edit)
114
edit_layout.addWidget(fhint)
116
self.listwidget = QListWidget(self)
117
self.listwidget.setResizeMode(QListWidget.Adjust)
118
self.connect(self.listwidget, SIGNAL("itemSelectionChanged()"),
119
self.item_selection_changed)
120
self.connect(self.listwidget, SIGNAL("itemActivated(QListWidgetItem*)"),
123
btn_layout = QHBoxLayout()
124
edit_btn = create_toolbutton(self, icon=get_icon('edit.png'),
125
text=translate("Editor", "&Edit file"), autoraise=False,
126
triggered=self.edit_file, text_beside_icon=True)
127
edit_btn.setMinimumHeight(28)
128
btn_layout.addWidget(edit_btn)
130
btn_layout.addStretch()
131
btn_layout.addSpacing(150)
132
btn_layout.addStretch()
134
close_btn = create_toolbutton(self,
135
text=translate("Editor", "&Close file"),
136
icon=get_icon("fileclose.png"),
137
autoraise=False, text_beside_icon=True,
138
triggered=lambda: self.emit(SIGNAL("close_file(int)"),
139
self.indexes[self.listwidget.currentRow()]))
140
close_btn.setMinimumHeight(28)
141
btn_layout.addWidget(close_btn)
143
hint = QLabel(translate("Editor",
144
"Hint: press <b>Alt</b> to show accelerators"))
145
hint.setAlignment(Qt.AlignCenter)
147
vlayout = QVBoxLayout()
148
vlayout.addLayout(edit_layout)
149
vlayout.addWidget(self.listwidget)
150
vlayout.addLayout(btn_layout)
151
vlayout.addWidget(hint)
152
self.setLayout(vlayout)
155
self.fullpath_sorting = fullpath_sorting
156
self.buttons = (edit_btn, close_btn)
159
row = self.listwidget.currentRow()
160
if self.listwidget.count() > 0 and row >= 0:
161
self.emit(SIGNAL("edit_file(int)"), self.indexes[row])
164
def item_selection_changed(self):
165
for btn in self.buttons:
166
btn.setEnabled(self.listwidget.currentRow() >= 0)
168
def synchronize(self, stack_index):
169
count = self.tabs.count()
173
self.listwidget.setTextElideMode(Qt.ElideMiddle if self.fullpath_sorting
175
current_row = self.listwidget.currentRow()
177
current_text = unicode(self.listwidget.currentItem().text())
180
self.listwidget.clear()
182
new_current_index = stack_index
183
filter_text = unicode(self.edit.text())
185
for index in range(count):
186
text = unicode(self.tabs.tabText(index))
187
if len(filter_text) == 0 or filter_text in text:
188
if text == current_text:
189
new_current_index = lw_index
191
item = QListWidgetItem(self.tabs.tabIcon(index),
192
text, self.listwidget)
193
item.setSizeHint(QSize(0, 25))
194
self.listwidget.addItem(item)
195
self.indexes.append(index)
196
if new_current_index < self.listwidget.count():
197
self.listwidget.setCurrentRow(new_current_index)
198
for btn in self.buttons:
199
btn.setEnabled(lw_index > 0)
50
202
class CodeAnalysisThread(QThread):
51
203
"""Pyflakes code analysis thread"""
52
def __init__(self, parent):
53
QThread.__init__(self, parent)
204
def __init__(self, editor):
205
QThread.__init__(self, editor)
54
207
self.filename = None
208
self.analysis_results = []
56
210
def set_filename(self, filename):
57
211
self.filename = filename
60
self.analysis_results = check(self.filename)
214
source_code = unicode(self.editor.toPlainText()).encode('utf-8')
215
self.analysis_results = check(source_code, filename=self.filename)
62
217
def get_results(self):
63
218
return self.analysis_results
96
254
self.analysis_results = []
97
255
self.todo_results = []
98
256
self.lastmodified = QFileInfo(filename).lastModified()
258
self.connect(editor, SIGNAL('trigger_code_completion(bool)'),
259
self.trigger_code_completion)
260
self.connect(editor, SIGNAL('trigger_calltip(int)'),
261
self.trigger_calltip)
262
self.connect(editor, SIGNAL("go_to_definition(int)"),
263
self.go_to_definition)
265
self.connect(editor, SIGNAL('textChanged()'),
268
self.connect(editor, SIGNAL('breakpoints_changed()'),
269
self.breakpoints_changed)
100
self.analysis_thread = CodeAnalysisThread(self)
271
self.analysis_thread = CodeAnalysisThread(self.editor)
101
272
self.connect(self.analysis_thread, SIGNAL('finished()'),
102
273
self.code_analysis_finished)
104
275
self.todo_thread = ToDoFinderThread(self)
105
276
self.connect(self.todo_thread, SIGNAL('finished()'),
106
277
self.todo_finished)
279
def close_threads(self):
280
self.is_closing = True
281
while self.analysis_thread.isRunning():
283
while self.todo_thread.isRunning():
286
def set_project(self, project):
287
self.project = project
289
def text_changed(self):
290
self.emit(SIGNAL('text_changed_at(QString,int)'),
291
self.filename, self.editor.get_position('cursor'))
293
def validate_project(self):
294
if self.project is None:
296
self.project.validate_rope_project()
298
def trigger_code_completion(self, automatic):
299
if self.project is None:
301
source_code = unicode(self.editor.toPlainText())
302
offset = self.editor.get_position('cursor')
304
textlist = self.project.get_completion_list(source_code, offset,
307
text = self.editor.get_text('sol', 'cursor')
308
completion_text = re.split(r"[^a-zA-Z0-9_]", text)[-1]
309
self.editor.show_completion_list(textlist, completion_text,
312
def trigger_calltip(self, position):
313
if self.project is None:
315
source_code = unicode(self.editor.toPlainText())
318
textlist = self.project.get_calltip_text(source_code, offset,
322
parpos = textlist[0].find('(')
324
text = textlist[0][:parpos]
326
text = get_primary_at(source_code, offset)
327
if text and not text.startswith('self.'):
329
if len(textlist) == 2:
330
doc_text = textlist.pop(1)
332
self.emit(SIGNAL("send_to_inspector(QString,QString)"),
335
self.editor.show_calltip("rope", textlist)
337
def go_to_definition(self, position):
338
if self.project is None:
340
source_code = unicode(self.editor.toPlainText())
342
fname, lineno = self.project.get_definition_location(source_code,
343
offset, self.filename)
344
if fname is not None and lineno is not None:
345
self.emit(SIGNAL("edit_goto(QString,int,QString)"),
108
348
def run_code_analysis(self):
109
if self.editor.is_python():
349
if self.editor.is_python() and not self.is_closing:
110
350
self.analysis_thread.set_filename(self.filename)
111
351
self.analysis_thread.start()
156
420
self.__get_split_actions()
158
422
layout = QVBoxLayout()
423
layout.setContentsMargins(0, 0, 0, 0)
159
424
self.setLayout(layout)
161
self.header_layout = None
164
self.default_combo_font = None
165
self.previous_btn = None
427
self.filelist_dlg = None
428
# self.filelist_btn = None
429
# self.previous_btn = None
430
# self.next_btn = None
168
self.close_btn = None
170
433
self.stack_history = []
172
self.setup_editorstack(parent, layout, actions)
435
self.setup_editorstack(parent, layout)
174
437
self.find_widget = None
178
self.menu_actions = actions
179
self.classbrowser = None
441
filelist_action = create_action(self,
442
translate("Editor", "File list management"),
443
icon=get_icon('filelist.png'),
444
triggered=self.open_filelistdialog)
445
copy_to_cb_action = create_action(self,
446
translate("Editor", "Copy path to clipboard"),
449
QApplication.clipboard().setText(self.get_current_filename()))
450
self.menu_actions = actions+[None, filelist_action, copy_to_cb_action]
451
self.outlineexplorer = None
452
self.projectexplorer = None
453
self.inspector = None
180
454
self.unregister_callback = None
181
455
self.is_closable = False
182
456
self.new_action = None
183
457
self.open_action = None
184
458
self.save_action = None
459
self.revert_action = None
185
460
self.tempfile_path = None
186
461
self.title = translate("Editor", "Editor")
187
462
self.filetype_filters = None
188
463
self.valid_types = None
189
464
self.codeanalysis_enabled = True
190
465
self.todolist_enabled = True
191
self.classbrowser_enabled = True
192
self.codefolding_enabled = True
466
self.realtime_analysis_enabled = False
467
self.is_analysis_done = False
468
self.linenumbers_enabled = True
469
self.edgeline_enabled = True
470
self.edgeline_column = 80
471
self.outlineexplorer_enabled = True
472
self.codecompletion_auto_enabled = False
473
self.codecompletion_case_enabled = False
474
self.codecompletion_single_enabled = False
475
self.codecompletion_enter_enabled = False
476
self.calltips_enabled = False
477
self.go_to_definition_enabled = False
478
self.close_parentheses_enabled = True
479
self.auto_unindent_enabled = True
480
self.inspector_enabled = False
193
481
self.default_font = None
194
482
self.wrap_enabled = False
195
483
self.tabmode_enabled = False
484
self.intelligent_backspace_enabled = True
485
self.highlight_current_line_enabled = False
196
486
self.occurence_highlighting_enabled = True
197
487
self.checkeolchars_enabled = True
198
488
self.fullpath_sorting_enabled = None
199
489
self.set_fullpath_sorting_enabled(False)
491
if ccs not in syntaxhighlighters.COLOR_SCHEME_NAMES:
492
ccs = syntaxhighlighters.COLOR_SCHEME_NAMES[0]
493
self.color_scheme = ccs
201
495
self.cursor_position_changed_callback = lambda line, index: \
202
496
self.emit(SIGNAL('cursorPositionChanged(int,int)'), line, index)
206
500
self.__file_status_flag = False
502
# Real-time code analysis
503
self.analysis_timer = QTimer(self)
504
self.analysis_timer.setSingleShot(True)
505
self.analysis_timer.setInterval(2000)
506
self.connect(self.analysis_timer, SIGNAL("timeout()"),
208
509
# Accepting drops
209
510
self.setAcceptDrops(True)
211
def setup_editorstack(self, parent, layout, actions):
513
self.inspectsc = QShortcut(QKeySequence("Ctrl+I"), self,
514
self.inspect_current_object)
515
self.inspectsc.setContext(Qt.WidgetWithChildrenShortcut)
516
self.breakpointsc = QShortcut(QKeySequence("F12"), self,
517
self.set_or_clear_breakpoint)
518
self.breakpointsc.setContext(Qt.WidgetWithChildrenShortcut)
519
self.cbreakpointsc = QShortcut(QKeySequence("Shift+F12"), self,
520
self.set_or_edit_conditional_breakpoint)
521
self.cbreakpointsc.setContext(Qt.WidgetWithChildrenShortcut)
522
self.gotolinesc = QShortcut(QKeySequence("Ctrl+L"), self,
524
self.gotolinesc.setContext(Qt.WidgetWithChildrenShortcut)
525
self.filelistsc = QShortcut(QKeySequence("Ctrl+E"), self,
526
self.open_filelistdialog)
527
self.filelistsc.setContext(Qt.WidgetWithChildrenShortcut)
528
self.tabsc = QShortcut(QKeySequence("Ctrl+Tab"), self,
529
self.go_to_previous_file)
530
self.tabsc.setContext(Qt.WidgetWithChildrenShortcut)
531
self.closesc = QShortcut(QKeySequence("Ctrl+F4"), self,
533
self.closesc.setContext(Qt.WidgetWithChildrenShortcut)
534
self.tabshiftsc = QShortcut(QKeySequence("Ctrl+Shift+Tab"), self,
535
self.go_to_next_file)
536
self.tabshiftsc.setContext(Qt.WidgetWithChildrenShortcut)
538
def get_shortcut_data(self):
540
Returns shortcut data, a list of tuples (shortcut, text, default)
541
shortcut (QShortcut or QAction instance)
542
text (string): action/shortcut description
543
default (string): default key sequence
546
(self.inspectsc, "Inspect current object", "Ctrl+I"),
547
(self.breakpointsc, "Breakpoint", "F12"),
548
(self.cbreakpointsc, "Conditional breakpoint", "Shift+F12"),
549
(self.gotolinesc, "Go to line", "Ctrl+L"),
550
(self.filelistsc, "File list management", "Ctrl+E"),
553
def setup_editorstack(self, parent, layout):
212
554
"""Setup editorstack's layout"""
213
self.header_layout = QHBoxLayout()
214
self.header_layout.setContentsMargins(0, 0, 0, 0)
216
# Buttons to the left of file combo box
217
555
menu_btn = create_toolbutton(self, icon=get_icon("tooloptions.png"),
218
556
tip=translate("Editor", "Options"))
219
557
self.menu = QMenu(self)
220
558
menu_btn.setMenu(self.menu)
221
559
menu_btn.setPopupMode(menu_btn.InstantPopup)
222
560
self.connect(self.menu, SIGNAL("aboutToShow()"), self.__setup_menu)
223
self.add_widget_to_header(menu_btn)
225
# newwin_btn = create_toolbutton(self, text_beside_icon=False)
226
# newwin_btn.setDefaultAction(self.newwindow_action)
227
# self.add_widget_to_header(newwin_btn)
229
# versplit_btn = create_toolbutton(self, text_beside_icon=False)
230
# versplit_btn.setDefaultAction(self.versplit_action)
231
# self.add_widget_to_header(versplit_btn)
233
# horsplit_btn = create_toolbutton(self, text_beside_icon=False)
234
# horsplit_btn.setDefaultAction(self.horsplit_action)
235
# self.add_widget_to_header(horsplit_btn)
237
self.previous_btn = create_toolbutton(self, get_icon('previous.png'),
238
tip=translate("Editor", "Previous file (Ctrl+Tab)"),
239
triggered=self.go_to_previous_file)
240
self.add_widget_to_header(self.previous_btn, space_before=True)
242
self.next_btn = create_toolbutton(self, get_icon('next.png'),
243
tip=translate("Editor", "Next file (Ctrl+Shift+Tab)"),
244
triggered=self.go_to_next_file)
245
self.add_widget_to_header(self.next_btn)
562
# self.filelist_btn = create_toolbutton(self,
563
# icon=get_icon('filelist.png'),
564
# tip=translate("Editor", "File list management"),
565
# triggered=self.open_filelistdialog)
567
# self.previous_btn = create_toolbutton(self,
568
# icon=get_icon('previous.png'),
569
# tip=translate("Editor", "Previous file"),
570
# triggered=self.go_to_previous_file)
572
# self.next_btn = create_toolbutton(self,
573
# icon=get_icon('next.png'),
574
# tip=translate("Editor", "Next file"),
575
# triggered=self.go_to_next_file)
248
self.combo = QComboBox(self)
249
self.default_combo_font = self.combo.font()
250
self.combo.setMaxVisibleItems(20)
251
self.combo.setSizeAdjustPolicy(QComboBox.AdjustToMinimumContentsLength)
252
self.combo.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
253
self.connect(self.combo, SIGNAL('currentIndexChanged(int)'),
254
self.current_changed)
255
self.add_widget_to_header(self.combo)
257
# Buttons to the right of file combo box
258
self.close_btn = create_toolbutton(self, triggered=self.close_file,
259
icon=get_icon("fileclose.png"),
260
tip=translate("Editor", "Close file"))
261
self.add_widget_to_header(self.close_btn)
262
layout.addLayout(self.header_layout)
265
tabsc = QShortcut(QKeySequence("Ctrl+Tab"), parent,
266
self.go_to_previous_file)
267
tabsc.setContext(Qt.WidgetWithChildrenShortcut)
268
tabshiftsc = QShortcut(QKeySequence("Ctrl+Shift+Tab"), parent,
269
self.go_to_next_file)
270
tabshiftsc.setContext(Qt.WidgetWithChildrenShortcut)
273
self.tabs = BaseTabs(self, menu=self.menu)
578
# corner_widgets = {Qt.TopRightCorner: [self.previous_btn,
579
# self.filelist_btn, self.next_btn,
581
corner_widgets = {Qt.TopRightCorner: [menu_btn]}
582
self.tabs = BaseTabs(self, menu=self.menu, menu_use_tooltips=True,
583
corner_widgets=corner_widgets)
274
584
self.tabs.set_close_function(self.close_file)
275
585
if hasattr(self.tabs, 'setDocumentMode'):
276
586
self.tabs.setDocumentMode(True)
277
self.connect(self.combo, SIGNAL('currentIndexChanged(int)'),
278
self.tabs.setCurrentIndex)
279
587
self.connect(self.tabs, SIGNAL('currentChanged(int)'),
280
self.combo.setCurrentIndex)
588
self.current_changed)
281
589
layout.addWidget(self.tabs)
283
def add_widget_to_header(self, widget, space_before=False):
285
self.header_layout.addSpacing(7)
286
self.header_layout.addWidget(widget)
591
def add_corner_widgets_to_tabbar(self, widgets):
592
self.tabs.add_corner_widgets(widgets)
288
594
def closeEvent(self, event):
289
super(EditorStack, self).closeEvent(event)
595
self.disconnect(self.analysis_timer, SIGNAL("timeout()"),
597
QWidget.closeEvent(self, event)
290
598
if PYQT_VERSION_STR.startswith('4.6'):
291
599
self.emit(SIGNAL('destroyed()'))
301
609
finfo.set_todo_results(other_finfo.todo_results)
302
610
self.set_stack_index(other.get_stack_index())
612
def open_filelistdialog(self):
613
"""Open file list management dialog box"""
614
self.filelist_dlg = dlg = FileListDialog(self, self.tabs,
615
self.fullpath_sorting_enabled)
616
self.connect(dlg, SIGNAL("edit_file(int)"), self.set_stack_index)
617
self.connect(dlg, SIGNAL("close_file(int)"), self.close_file)
618
dlg.synchronize(self.get_stack_index())
620
self.filelist_dlg = None
622
def update_filelistdialog(self):
623
"""Synchronize file list dialog box with editor widget tabs"""
624
if self.filelist_dlg is not None:
625
self.filelist_dlg.synchronize(self.get_stack_index())
627
def go_to_line(self):
628
"""Go to line dialog"""
630
editor = self.get_current_editor()
631
dlg = GoToLineDialog(editor)
633
editor.go_to_line(dlg.get_line_number())
635
def set_or_clear_breakpoint(self):
636
"""Set/clear breakpoint"""
638
editor = self.get_current_editor()
639
editor.add_remove_breakpoint()
641
def set_or_edit_conditional_breakpoint(self):
642
"""Set conditional breakpoint"""
644
editor = self.get_current_editor()
645
editor.add_remove_breakpoint(edit_condition=True)
647
def inspect_current_object(self):
648
"""Inspect current object in Object Inspector"""
649
if programs.is_module_installed('rope'):
650
editor = self.get_current_editor()
651
position = editor.get_position('cursor')
652
editor.emit(SIGNAL('trigger_calltip(int)'), position)
654
text = self.get_current_editor().get_current_object()
656
self.send_to_inspector(text)
304
659
#------ Editor Widget Settings
305
660
def set_closable(self, state):
306
661
"""Parent widget must handle the closable state"""
307
662
self.is_closable = state
309
def set_io_actions(self, new_action, open_action, save_action):
664
def set_io_actions(self, new_action, open_action,
665
save_action, revert_action):
310
666
self.new_action = new_action
311
667
self.open_action = open_action
312
668
self.save_action = save_action
669
self.revert_action = revert_action
314
671
def set_find_widget(self, find_widget):
315
672
self.find_widget = find_widget
317
def set_classbrowser(self, classbrowser):
318
self.classbrowser = classbrowser
319
self.classbrowser_enabled = True
320
self.connect(self.classbrowser, SIGNAL("classbrowser_is_visible()"),
321
self._refresh_classbrowser)
674
def set_outlineexplorer(self, outlineexplorer):
675
self.outlineexplorer = outlineexplorer
676
self.outlineexplorer_enabled = True
677
self.connect(self.outlineexplorer,
678
SIGNAL("outlineexplorer_is_visible()"),
679
self._refresh_outlineexplorer)
681
def set_projectexplorer(self, projectexplorer):
682
self.projectexplorer = projectexplorer
683
for finfo in self.data:
684
project = self.projectexplorer.get_source_project(finfo.filename)
685
finfo.set_project(project)
687
def set_inspector(self, inspector):
688
self.inspector = inspector
323
690
def set_tempfile_path(self, path):
324
691
self.tempfile_path = path
359
725
if state and current_finfo is not None:
360
726
if current_finfo is not finfo:
361
727
finfo.run_todo_finder()
363
def set_codefolding_enabled(self, state):
364
# CONF.get(self.ID, 'code_folding')
365
self.codefolding_enabled = state
729
def set_realtime_analysis_enabled(self, state):
730
self.realtime_analysis_enabled = state
732
def set_realtime_analysis_timeout(self, timeout):
733
self.analysis_timer.setInterval(timeout)
735
def set_linenumbers_enabled(self, state, current_finfo=None):
736
# CONF.get(self.CONF_SECTION, 'line_numbers')
737
self.linenumbers_enabled = state
367
739
for finfo in self.data:
368
740
self.__update_editor_margins(finfo.editor)
370
finfo.editor.unfold_all()
372
def set_classbrowser_enabled(self, state):
373
# CONF.get(self.ID, 'class_browser')
374
self.classbrowser_enabled = state
376
def set_default_font(self, font):
742
def set_edgeline_enabled(self, state):
743
# CONF.get(self.CONF_SECTION, 'edge_line')
744
self.edgeline_enabled = state
746
for finfo in self.data:
747
finfo.editor.set_edge_line_enabled(state)
749
def set_edgeline_column(self, column):
750
# CONF.get(self.CONF_SECTION, 'edge_line_column')
751
self.edgeline_column = column
753
for finfo in self.data:
754
finfo.editor.set_edge_line_column(column)
756
def set_codecompletion_auto_enabled(self, state):
757
# CONF.get(self.CONF_SECTION, 'codecompletion_auto')
758
self.codecompletion_auto_enabled = state
760
for finfo in self.data:
761
finfo.editor.set_codecompletion_auto(state)
763
def set_codecompletion_case_enabled(self, state):
764
self.codecompletion_case_enabled = state
766
for finfo in self.data:
767
finfo.editor.set_codecompletion_case(state)
769
def set_codecompletion_single_enabled(self, state):
770
self.codecompletion_single_enabled = state
772
for finfo in self.data:
773
finfo.editor.set_codecompletion_single(state)
775
def set_codecompletion_enter_enabled(self, state):
776
self.codecompletion_enter_enabled = state
778
for finfo in self.data:
779
finfo.editor.set_codecompletion_enter(state)
781
def set_calltips_enabled(self, state):
782
# CONF.get(self.CONF_SECTION, 'calltips')
783
self.calltips_enabled = state
785
for finfo in self.data:
786
finfo.editor.set_calltips(state)
788
def set_go_to_definition_enabled(self, state):
789
# CONF.get(self.CONF_SECTION, 'go_to_definition')
790
self.go_to_definition_enabled = state
792
for finfo in self.data:
793
finfo.editor.set_go_to_definition_enabled(state)
795
def set_close_parentheses_enabled(self, state):
796
# CONF.get(self.CONF_SECTION, 'close_parentheses')
797
self.close_parentheses_enabled = state
799
for finfo in self.data:
800
finfo.editor.set_close_parentheses_enabled(state)
802
def set_auto_unindent_enabled(self, state):
803
# CONF.get(self.CONF_SECTION, 'auto_unindent')
804
self.auto_unindent_enabled = state
806
for finfo in self.data:
807
finfo.editor.set_auto_unindent_enabled(state)
809
def set_inspector_enabled(self, state):
810
self.inspector_enabled = state
812
def set_outlineexplorer_enabled(self, state):
813
# CONF.get(self.CONF_SECTION, 'outline_explorer')
814
self.outlineexplorer_enabled = state
816
def set_default_font(self, font, color_scheme=None):
817
# get_font(self.CONF_SECTION)
378
818
self.default_font = font
379
self.__update_combobox()
381
for finfo in self.data:
382
finfo.editor.set_font(font)
819
if color_scheme is not None:
820
self.color_scheme = color_scheme
822
for finfo in self.data:
823
finfo.editor.set_font(font, color_scheme)
825
def set_color_scheme(self, color_scheme):
826
self.color_scheme = color_scheme
828
for finfo in self.data:
829
finfo.editor.set_color_scheme(color_scheme)
384
831
def set_wrap_enabled(self, state):
385
# CONF.get(self.ID, 'wrap')
832
# CONF.get(self.CONF_SECTION, 'wrap')
386
833
self.wrap_enabled = state
388
835
for finfo in self.data:
389
836
finfo.editor.toggle_wrap_mode(state)
391
838
def set_tabmode_enabled(self, state):
392
# CONF.get(self.ID, 'tab_always_indent'))
839
# CONF.get(self.CONF_SECTION, 'tab_always_indent')
393
840
self.tabmode_enabled = state
395
842
for finfo in self.data:
396
843
finfo.editor.set_tab_mode(state)
845
def set_intelligent_backspace_enabled(self, state):
846
# CONF.get(self.CONF_SECTION, 'intelligent_backspace')
847
self.intelligent_backspace_enabled = state
849
for finfo in self.data:
850
finfo.editor.toggle_intelligent_backspace(state)
398
852
def set_occurence_highlighting_enabled(self, state):
399
# CONF.get(self.ID, 'occurence_highlighting'))
853
# CONF.get(self.CONF_SECTION, 'occurence_highlighting')
400
854
self.occurence_highlighting_enabled = state
402
856
for finfo in self.data:
403
857
finfo.editor.set_occurence_highlighting(state)
859
def set_highlight_current_line_enabled(self, state):
860
self.highlight_current_line_enabled = state
862
for finfo in self.data:
863
finfo.editor.set_highlight_current_line(state)
405
865
def set_checkeolchars_enabled(self, state):
406
# CONF.get(self.ID, 'check_eol_chars')
866
# CONF.get(self.CONF_SECTION, 'check_eol_chars')
407
867
self.checkeolchars_enabled = state
409
def __update_combobox(self):
410
if self.fullpath_sorting_enabled:
411
if self.default_font is not None:
412
combo_font = QFont(self.default_font)
413
combo_font.setPointSize(combo_font.pointSize()-1)
414
self.combo.setFont(combo_font)
415
self.combo.setEditable(True)
416
self.combo.lineEdit().setReadOnly(True)
418
self.combo.setFont(self.default_combo_font)
419
self.combo.setEditable(False)
421
869
def set_fullpath_sorting_enabled(self, state):
422
# CONF.get(self.ID, 'fullpath_sorting')
870
# CONF.get(self.CONF_SECTION, 'fullpath_sorting')
423
871
self.fullpath_sorting_enabled = state
424
self.__update_combobox()
426
873
finfo = self.data[self.get_stack_index()]
427
874
self.data.sort(key=self.__get_sorting_func())
428
875
new_index = self.data.index(finfo)
429
876
self.__repopulate_stack()
430
877
self.set_stack_index(new_index)
432
880
#------ Stacked widget management
433
881
def get_stack_index(self):
434
882
return self.tabs.currentIndex()
436
884
def get_current_finfo(self):
437
return self.data[self.get_stack_index()]
886
return self.data[self.get_stack_index()]
439
888
def get_current_editor(self):
440
889
return self.tabs.currentWidget()
443
892
return self.tabs.count()
445
894
def set_stack_index(self, index):
446
for widget in (self.tabs, self.combo):
447
widget.setCurrentIndex(index)
895
self.tabs.setCurrentIndex(index)
449
897
def set_tabbar_visible(self, state):
450
898
self.tabs.tabBar().setVisible(state)
452
900
def remove_from_data(self, index):
901
self.tabs.blockSignals(True)
453
902
self.tabs.removeTab(index)
454
903
self.data.pop(index)
455
self.combo.removeItem(index)
904
self.tabs.blockSignals(False)
456
905
self.update_actions()
906
self.update_filelistdialog()
908
def __modified_readonly_title(self, title, is_modified, is_readonly):
909
if is_modified is not None and is_modified:
911
if is_readonly is not None and is_readonly:
912
title = "(%s)" % title
915
def get_tab_text(self, filename, is_modified=None, is_readonly=None):
916
"""Return tab title"""
917
return self.__modified_readonly_title(osp.basename(filename),
918
is_modified, is_readonly)
920
def get_tab_tip(self, filename, is_modified=None, is_readonly=None):
921
"""Return tab menu title"""
922
if self.fullpath_sorting_enabled:
926
text = self.__modified_readonly_title(text,
927
is_modified, is_readonly)
928
if filename == encoding.to_unicode(self.tempfile_path):
929
temp_file_str = unicode(translate("Editor", "Temporary file"))
930
if self.fullpath_sorting_enabled:
931
return "%s (%s)" % (text, temp_file_str)
933
return text % (temp_file_str, self.tempfile_path)
935
if self.fullpath_sorting_enabled:
938
return text % (osp.basename(filename), osp.dirname(filename))
458
940
def __get_sorting_func(self):
459
941
if self.fullpath_sorting_enabled:
460
942
return lambda item: osp.join(osp.dirname(item.filename),
467
949
self.data.sort(key=self.__get_sorting_func())
468
950
index = self.data.index(finfo)
469
951
fname, editor = finfo.filename, finfo.editor
470
self.combo.blockSignals(True)
471
952
self.tabs.insertTab(index, editor, get_filetype_icon(fname),
472
self.get_tab_title(fname))
473
self.combo.insertItem(index, get_filetype_icon(fname),
474
self.get_combo_title(fname))
953
self.get_tab_text(fname))
954
self.set_stack_title(index, False)
476
956
self.set_stack_index(index)
477
self.combo.blockSignals(False)
479
958
self.current_changed(index)
480
959
self.update_actions()
960
self.update_filelistdialog()
482
962
def __repopulate_stack(self):
483
self.combo.blockSignals(True)
484
for _i in range(self.tabs.count()):
485
self.tabs.removeTab(_i)
487
for _i, _fi in enumerate(self.data):
488
fname, editor = _fi.filename, _fi.editor
489
self.tabs.insertTab(_i, editor, get_filetype_icon(fname),
490
self.get_tab_title(fname))
491
self.combo.insertItem(_i, get_filetype_icon(fname),
492
self.get_combo_title(fname))
493
self.combo.blockSignals(False)
963
self.tabs.blockSignals(True)
965
for finfo in self.data:
966
icon = get_filetype_icon(finfo.filename)
967
tab_text = self.get_tab_text(finfo.filename)
968
tab_tip = self.get_tab_tip(finfo.filename)
969
index = self.tabs.addTab(finfo.editor, icon, tab_text)
970
self.tabs.setTabToolTip(index, tab_tip)
971
self.tabs.blockSignals(False)
972
self.update_filelistdialog()
495
974
def rename_in_data(self, index, new_filename):
496
975
finfo = self.data[index]
976
if osp.splitext(finfo.filename)[1] != osp.splitext(new_filename)[1]:
977
# File type has changed!
978
language = get_file_language(new_filename)
979
finfo.editor.set_language(language)
497
980
set_new_index = index == self.get_stack_index()
498
981
finfo.filename = new_filename
499
982
self.data.sort(key=self.__get_sorting_func())
696
1186
index = self.get_stack_index()
698
1188
finfo = self.data[index]
699
if not finfo.editor.isModified() and not force:
1189
if not finfo.editor.document().isModified() and not force:
701
1191
if not osp.isfile(finfo.filename) and not force:
702
1192
# File has not been saved yet
703
filename = self.select_savename(finfo.filename)
705
finfo.filename = filename
708
txt = unicode(finfo.editor.get_text())
1193
return self.save_as(index=index)
1194
txt = unicode(finfo.editor.get_text_with_eol())
710
1196
finfo.encoding = encoding.write(txt, finfo.filename, finfo.encoding)
711
1197
finfo.newly_created = False
712
1198
self.emit(SIGNAL('encoding_changed(QString)'), finfo.encoding)
713
1199
finfo.lastmodified = QFileInfo(finfo.filename).lastModified()
714
1200
self.emit(SIGNAL('file_saved(int)'), index)
715
finfo.editor.setModified(False)
1201
finfo.editor.document().setModified(False)
716
1202
self.modification_changed(index=index)
717
1203
self.analyze_script(index)
1204
finfo.validate_project()
719
#XXX QtEditor-only: re-scan the whole text to rebuild class browser
1206
#XXX CodeEditor-only: re-scan the whole text to rebuild outline explorer
720
1207
# data from scratch (could be optimized because rehighlighting
721
1208
# text means searching for all syntax coloring patterns instead
722
1209
# of only searching for class/def patterns which would be
723
# sufficient for class browser data.
1210
# sufficient for outline explorer data.
724
1211
finfo.editor.rehighlight()
726
self._refresh_classbrowser(index)
1213
self._refresh_outlineexplorer(index)
727
1214
if refresh_explorer:
728
1215
# Refresh the explorer widget if it exists:
729
1216
self.emit(SIGNAL("refresh_explorer(QString)"),
756
1243
return osp.normpath(unicode(filename))
1245
def save_as(self, index=None):
759
1246
"""Save file as..."""
760
index = self.get_stack_index()
1248
# Save the currently edited file
1249
index = self.get_stack_index()
761
1250
finfo = self.data[index]
762
1251
filename = self.select_savename(finfo.filename)
764
1253
ao_index = self.has_filename(filename)
1254
# Note: ao_index == index --> saving an untitled file
1255
if ao_index and ao_index != index:
766
1256
if not self.close_file(ao_index):
768
1258
if ao_index < index:
770
1260
new_index = self.rename_in_data(index, new_filename=filename)
771
self.save(index=new_index, force=True)
1261
ok = self.save(index=new_index, force=True)
772
1262
self.refresh(new_index)
773
1263
self.set_stack_index(new_index)
775
1268
def save_all(self):
776
1269
"""Save all opened files"""
778
1271
for index in range(self.get_stack_count()):
779
folders.add(osp.dirname(self.data[index].filename))
780
self.save(index, refresh_explorer=False)
1272
if self.data[index].editor.document().isModified():
1273
folders.add(osp.dirname(self.data[index].filename))
1274
self.save(index, refresh_explorer=False)
781
1275
for folder in folders:
782
1276
self.emit(SIGNAL("refresh_explorer(QString)"), folder)
784
1278
#------ Update UI
1279
def start_stop_analysis_timer(self):
1280
self.is_analysis_done = False
1281
if self.realtime_analysis_enabled:
1282
self.analysis_timer.stop()
1283
self.analysis_timer.start()
785
1285
def analyze_script(self, index=None):
786
1286
"""Analyze current script with pyflakes + find todos"""
1287
if self.is_analysis_done:
787
1289
if index is None:
788
1290
index = self.get_stack_index()
986
1491
# Update FindReplace binding
987
1492
self.find_widget.set_editor(editor, refresh=False)
989
def __modified_readonly_title(self, title, is_modified, is_readonly):
990
if is_modified is not None and is_modified:
992
if is_readonly is not None and is_readonly:
993
title = "(%s)" % title
996
def get_tab_title(self, filename, is_modified=None, is_readonly=None):
997
"""Return tab title"""
998
return self.__modified_readonly_title(osp.basename(filename),
999
is_modified, is_readonly)
1001
def get_combo_title(self, filename, is_modified=None, is_readonly=None):
1002
"""Return combo box title"""
1003
if self.fullpath_sorting_enabled:
1007
text = self.__modified_readonly_title(text,
1008
is_modified, is_readonly)
1009
if filename == encoding.to_unicode(self.tempfile_path):
1010
temp_file_str = unicode(translate("Editor", "Temporary file"))
1011
if self.fullpath_sorting_enabled:
1012
return "%s (%s)" % (text, temp_file_str)
1014
return text % (temp_file_str, self.tempfile_path)
1016
if self.fullpath_sorting_enabled:
1019
return text % (osp.basename(filename), osp.dirname(filename))
1021
def get_titles(self, is_modified, finfo):
1022
"""Return combo box and tab titles"""
1023
fname = finfo.filename
1024
is_readonly = finfo.editor.isReadOnly()
1025
combo_title = self.get_combo_title(fname, is_modified, is_readonly)
1026
tab_title = self.get_tab_title(fname, is_modified, is_readonly)
1027
return combo_title, tab_title
1029
1494
def modification_changed(self, state=None, index=None):
1031
1496
Current editor's modification state has changed
1075
1552
Create a new editor instance
1076
1553
Returns finfo object (instead of editor as in previous releases)
1078
ext = osp.splitext(fname)[1]
1079
if ext.startswith('.'):
1080
ext = ext[1:] # file extension with leading dot
1083
for line in txt.splitlines():
1084
if not line.strip():
1086
if line.startswith('#!') and \
1087
line[2:].split() == ['/usr/bin/env', 'python']:
1091
1555
editor = CodeEditor(self)
1092
finfo = TabInfo(fname, enc, editor, new)
1556
finfo = FileInfo(fname, enc, editor, new)
1557
if self.projectexplorer is not None:
1558
finfo.set_project(self.projectexplorer.get_source_project(fname))
1093
1559
self.add_to_data(finfo, set_current)
1560
self.connect(finfo, SIGNAL("send_to_inspector(QString,QString)"),
1561
self.send_to_inspector)
1094
1562
self.connect(finfo, SIGNAL('analysis_results_changed()'),
1095
1563
lambda: self.emit(SIGNAL('analysis_results_changed()')))
1096
1564
self.connect(finfo, SIGNAL('todo_results_changed()'),
1097
1565
lambda: self.emit(SIGNAL('todo_results_changed()')))
1098
editor.setup_editor(linenumbers=True, language=language,
1566
self.connect(finfo, SIGNAL("edit_goto(QString,int,QString)"),
1567
lambda fname, lineno, name:
1568
self.emit(SIGNAL("edit_goto(QString,int,QString)"),
1569
fname, lineno, name))
1570
self.connect(finfo, SIGNAL("save_breakpoints(QString,QString)"),
1572
self.emit(SIGNAL("save_breakpoints(QString,QString)"),
1574
language = get_file_language(fname, txt)
1575
editor.setup_editor(
1576
linenumbers=self.linenumbers_enabled,
1577
edge_line=self.edgeline_enabled,
1578
edge_line_column=self.edgeline_column, language=language,
1099
1579
code_analysis=self.codeanalysis_enabled,
1100
code_folding=self.codefolding_enabled,
1101
1580
todo_list=self.todolist_enabled, font=self.default_font,
1581
color_scheme=self.color_scheme,
1102
1582
wrap=self.wrap_enabled, tab_mode=self.tabmode_enabled,
1583
intelligent_backspace=self.intelligent_backspace_enabled,
1584
highlight_current_line=self.highlight_current_line_enabled,
1103
1585
occurence_highlighting=self.occurence_highlighting_enabled,
1586
codecompletion_auto=self.codecompletion_auto_enabled,
1587
codecompletion_case=self.codecompletion_case_enabled,
1588
codecompletion_single=self.codecompletion_single_enabled,
1589
codecompletion_enter=self.codecompletion_enter_enabled,
1590
calltips=self.calltips_enabled,
1591
go_to_definition=self.go_to_definition_enabled,
1592
close_parentheses=self.close_parentheses_enabled,
1593
auto_unindent=self.auto_unindent_enabled,
1104
1594
cloned_from=cloned_from)
1105
1595
if cloned_from is None:
1106
1596
editor.set_text(txt)
1107
editor.setModified(False)
1597
editor.document().setModified(False)
1598
self.connect(finfo, SIGNAL('text_changed_at(QString,int)'),
1599
lambda fname, position:
1600
self.emit(SIGNAL('text_changed_at(QString,int)'),
1108
1602
self.connect(editor, SIGNAL('cursorPositionChanged(int,int)'),
1109
1603
self.cursor_position_changed_callback)
1604
self.connect(editor, SIGNAL('textChanged()'),
1605
self.start_stop_analysis_timer)
1110
1606
self.connect(editor, SIGNAL('modificationChanged(bool)'),
1111
1607
self.modification_changed)
1112
1608
self.connect(editor, SIGNAL("focus_in()"), self.focus_changed)
1113
1609
self.connect(editor, SIGNAL("focus_changed()"),
1114
1610
self.focus_changed_callback)
1115
if self.classbrowser is not None:
1116
# Removing editor reference from class browser settings:
1611
if self.outlineexplorer is not None:
1612
# Removing editor reference from outline explorer settings:
1117
1613
self.connect(editor, SIGNAL("destroyed()"),
1118
1614
lambda obj=editor:
1119
self.classbrowser.remove_editor(obj))
1615
self.outlineexplorer.remove_editor(obj))
1121
1617
self.find_widget.set_editor(editor)
1210
1719
text = ls.join([line[min_indent:] for line in text.split(ls)])
1212
1721
last_line = text.split(ls)[-1]
1213
if last_line.strip() == unicode(editor.text(line_to)).strip():
1722
if last_line.strip() == editor.get_text_line(line_to).strip():
1214
1723
# If last line is complete, add an EOL character
1219
def __run_in_interactive_console(self, lines):
1220
self.emit(SIGNAL('interactive_console_execute_lines(QString)'), lines)
1222
1728
def __run_in_external_console(self, lines):
1223
1729
self.emit(SIGNAL('external_console_execute_lines(QString)'), lines)
1225
def run_selection_or_block(self, external=False):
1731
def run_selection_or_block(self):
1227
1733
Run selected text in console and set focus to console
1228
1734
*or*, if there is no selection,
1229
1735
Run current block of lines in console and go to next block
1232
run_callback = self.__run_in_external_console
1234
run_callback = self.__run_in_interactive_console
1235
1737
editor = self.get_current_editor()
1236
if editor.hasSelectedText():
1237
# Run selected text in interactive console and set focus to console
1238
run_callback( self.__process_lines() )
1738
if editor.has_selected_text():
1739
# Run selected text in external console and set focus to console
1740
self.__run_in_external_console( self.__process_lines() )
1240
# Run current block in interactive console and go to next block
1742
# Run current block in external console and go to next block
1241
1743
editor.select_current_block()
1242
run_callback( self.__process_lines() )
1744
self.__run_in_external_console( self.__process_lines() )
1243
1745
editor.setFocus()
1244
editor.move_cursor_to_next('block', 'down')
1746
editor.clear_selection()
1246
1748
#------ Drag and drop
1247
1749
def dragEnterEvent(self, event):