~ubuntu-branches/ubuntu/wily/spyder/wily

« back to all changes in this revision

Viewing changes to spyderlib/widgets/sourcecode/codeeditor.py

  • Committer: Package Import Robot
  • Author(s): Benjamin Drung
  • Date: 2015-01-15 12:20:11 UTC
  • mfrom: (18.1.7 experimental)
  • Revision ID: package-import@ubuntu.com-20150115122011-cc7j5dhy2h9uo13m
Tags: 2.3.2+dfsg-1ubuntu1
Backport patch to support pylint3.

Show diffs side-by-side

added added

removed removed

Lines of Context:
30
30
                                QInputDialog, QTextBlockUserData, QLineEdit,
31
31
                                QKeySequence, QWidget, QVBoxLayout,
32
32
                                QHBoxLayout, QDialog, QIntValidator,
33
 
                                QDialogButtonBox, QGridLayout)
34
 
from spyderlib.qt.QtCore import (Qt, SIGNAL, QTimer, QRect, QRegExp, QSize,
35
 
                                 SLOT, Slot)
 
33
                                QDialogButtonBox, QGridLayout, QPaintEvent)
 
34
from spyderlib.qt.QtCore import (Qt, SIGNAL, Signal, QTimer, QRect, QRegExp,
 
35
                                 QSize, SLOT, Slot)
36
36
from spyderlib.qt.compat import to_qvariant
37
37
 
38
38
#%% This line is for cell execution testing
45
45
from spyderlib.utils.qthelpers import (add_actions, create_action, keybinding,
46
46
                                       mimedata2url, get_icon)
47
47
from spyderlib.utils.dochelpers import getobj
48
 
from spyderlib.utils import encoding, sourcecode
49
 
from spyderlib.utils.sourcecode import ALL_LANGUAGES
 
48
from spyderlib.utils import encoding, sourcecode, programs
 
49
from spyderlib.utils.sourcecode import ALL_LANGUAGES, CELL_LANGUAGES
50
50
from spyderlib.widgets.editortools import PythonCFM
51
51
from spyderlib.widgets.sourcecode.base import TextEditBaseWidget
52
52
from spyderlib.widgets.sourcecode import syntaxhighlighters as sh
53
53
from spyderlib.py3compat import to_text_string
54
54
 
 
55
if programs.is_module_installed('IPython'):
 
56
    import IPython.nbformat as nbformat
 
57
    import IPython.nbformat.current  # in IPython 0.13.2, current is not loaded
 
58
                                     # with nbformat.
 
59
    try:
 
60
        from IPython.nbconvert import PythonExporter as nbexporter  # >= 1.0
 
61
    except:
 
62
        nbexporter = None
 
63
else:
 
64
    nbformat = None
 
65
 
55
66
#%% This line is for cell execution testing
56
67
# For debugging purpose:
57
68
LOG_FILENAME = get_conf_path('codeeditor.log')
158
169
        """Override Qt method"""
159
170
        self.code_editor.linenumberarea_mousedoubleclick_event(event)
160
171
 
 
172
    def mousePressEvent(self, event):
 
173
        """Override Qt method"""
 
174
        self.code_editor.linenumberarea_mousepress_event(event)
 
175
 
 
176
    def mouseReleaseEvent(self, event):
 
177
        """Override Qt method"""
 
178
        self.code_editor.linenumberarea_mouserelease_event(event)
 
179
 
 
180
    def wheelEvent(self, event):
 
181
        """Override Qt method"""
 
182
        self.code_editor.wheelEvent(event)
 
183
 
161
184
 
162
185
class ScrollFlagArea(QWidget):
163
186
    """Source code editor's scroll flag area"""
219
242
        pos2 = self.value_to_position(value + vsb.pageStep(), slider=True)
220
243
        return QRect(1, pos1, self.WIDTH-2, pos2-pos1+1)
221
244
 
 
245
    def wheelEvent(self, event):
 
246
        """Override Qt method"""
 
247
        self.code_editor.wheelEvent(event)
 
248
 
222
249
 
223
250
class EdgeLine(QWidget):
224
251
    """Source code editor's edge line (default: 79 columns, PEP8)"""
286
313
 
287
314
class CodeEditor(TextEditBaseWidget):
288
315
    """Source Code Editor Widget based exclusively on Qt"""
289
 
    LANGUAGES ={ 'Python': (sh.PythonSH, '#', PythonCFM),
 
316
    
 
317
    LANGUAGES = {'Python': (sh.PythonSH, '#', PythonCFM),
290
318
                 'Cython': (sh.CythonSH, '#', PythonCFM),
291
319
                 'Fortran77': (sh.Fortran77SH, 'c', None),
292
320
                 'Fortran': (sh.FortranSH, '!', None),
299
327
                 'Css': (sh.CssSH, '', None),
300
328
                 'Xml': (sh.XmlSH, '', None),
301
329
                 'Js': (sh.JsSH, '//', None),
 
330
                 'Json': (sh.JsonSH, '', None),
302
331
                 'Julia': (sh.JuliaSH, '#', None),
 
332
                 'Yaml': (sh.YamlSH, '#', None),
303
333
                 'Cpp': (sh.CppSH, '//', None),
304
334
                 'OpenCL': (sh.OpenCLSH, '//', None),
305
335
                 'Batch': (sh.BatchSH, 'rem ', None),
306
336
                 'Ini': (sh.IniSH, '#', None),
307
337
                 'Enaml': (sh.EnamlSH, '#', PythonCFM),
308
 
                 }
 
338
                }
 
339
 
309
340
    try:
310
341
        import pygments  # analysis:ignore
311
342
    except ImportError:
316
347
    
317
348
    TAB_ALWAYS_INDENTS = ('py', 'pyw', 'python', 'c', 'cpp', 'cl', 'h')
318
349
 
 
350
    # Custom signal to be emitted upon completion of the editor's paintEvent 
 
351
    painted = Signal(QPaintEvent)
 
352
    
 
353
    # To have these attrs when early viewportEvent's are triggered
 
354
    edge_line = None
 
355
    linenumberarea = None
 
356
    
319
357
    def __init__(self, parent=None):
320
358
        TextEditBaseWidget.__init__(self, parent)
321
359
        self.setFocusPolicy(Qt.StrongFocus)
329
367
        # Caret (text cursor)
330
368
        self.setCursorWidth( CONF.get('editor_appearance', 'cursor/width') )
331
369
 
332
 
        # Side areas background color
333
 
        self.area_background_color = QColor(Qt.white)
334
 
 
335
370
        # 79-col edge line
336
371
        self.edge_line_enabled = True
337
372
        self.edge_line = EdgeLine(self)
354
389
                     self.update_linenumberarea_width)
355
390
        self.connect(self, SIGNAL("updateRequest(QRect,int)"),
356
391
                     self.update_linenumberarea)
 
392
        self.linenumberarea_pressed = -1
 
393
        self.linenumberarea_released = -1
357
394
 
 
395
        # Colors to be defined in _apply_highlighter_color_scheme()
 
396
        # Currentcell color and current line color are defined in base.py
 
397
        self.occurence_color = None
 
398
        self.ctrl_click_color = None
 
399
        self.sideareas_color = None
 
400
        self.matched_p_color = None
 
401
        self.unmatched_p_color = None
 
402
        self.normal_color = None
 
403
        self.comment_color = None
 
404
        
 
405
        self.linenumbers_color = QColor(Qt.darkGray)
358
406
 
359
407
        # --- Syntax highlight entrypoint ---
360
408
        #
397
445
        self.__find_first_pos = None
398
446
        self.__find_flags = None
399
447
 
400
 
        self.supported_language = None
 
448
        self.supported_language = False
 
449
        self.supported_cell_language = False
401
450
        self.classfunc_match = None
402
451
        self.comment_string = None
403
452
 
444
493
        self.setMouseTracking(True)
445
494
        self.__cursor_changed = False
446
495
        self.ctrl_click_color = QColor(Qt.blue)
447
 
        
 
496
 
448
497
        # Breakpoints
449
498
        self.breakpoints = self.get_breakpoints()
450
499
 
451
500
        # Keyboard shortcuts
452
501
        self.shortcuts = self.create_shortcuts()
453
502
 
 
503
        # Code editor
 
504
        self.__visible_blocks = []  # Visible blocks, update with repaint
 
505
        self.painted.connect(self._draw_editor_cell_divider)
 
506
        
 
507
        self.connect(self.verticalScrollBar(), SIGNAL('valueChanged(int)'),
 
508
                     lambda value: self.rehighlight_cells())
 
509
 
454
510
    def create_shortcuts(self):
455
511
        codecomp = create_shortcut(self.do_code_completion, context='Editor',
456
512
                                   name='Code completion', parent=self)
584
640
        """Enable/Disable go-to-definition feature, which is implemented in
585
641
        child class -> Editor widget"""
586
642
        self.go_to_definition_enabled = enable
587
 
        self.gotodef_action.setEnabled(enable)
588
643
 
589
644
    def set_close_parentheses_enabled(self, enable):
590
645
        """Enable/disable automatic parentheses insertion feature"""
622
677
 
623
678
    def set_highlight_current_cell(self, enable):
624
679
        """Enable/disable current line highlighting"""
625
 
        self.highlight_current_cell_enabled = enable
 
680
        hl_cell_enable = enable and self.supported_cell_language
 
681
        self.highlight_current_cell_enabled = hl_cell_enable
626
682
        if self.highlight_current_cell_enabled:
627
683
            self.highlight_current_cell()
628
684
        else:
630
686
 
631
687
    def set_language(self, language):
632
688
        self.tab_indents = language in self.TAB_ALWAYS_INDENTS
633
 
        self.supported_language = False
634
689
        self.comment_string = ''
635
690
        sh_class = sh.TextSH
636
691
        if language is not None:
639
694
                    self.supported_language = True
640
695
                    sh_class, comment_string, CFMatch = self.LANGUAGES[key]
641
696
                    self.comment_string = comment_string
 
697
                    if key in CELL_LANGUAGES:
 
698
                        self.supported_cell_language = True
 
699
                        self.cell_separators = CELL_LANGUAGES[key]
642
700
                    if CFMatch is None:
643
701
                        self.classfunc_match = None
644
702
                    else:
647
705
        self._set_highlighter(sh_class)
648
706
 
649
707
    def _set_highlighter(self, sh_class):
650
 
        if self.highlighter_class is not sh_class:
651
 
            self.highlighter_class = sh_class
652
 
            if self.highlighter is not None:
653
 
                # Removing old highlighter
654
 
                # TODO: test if leaving parent/document as is eats memory
655
 
                self.highlighter.setParent(None)
656
 
                self.highlighter.setDocument(None)
657
 
            self.highlighter = self.highlighter_class(self.document(),
658
 
                                                self.font(), self.color_scheme)
659
 
            self._apply_highlighter_color_scheme()
 
708
        self.highlighter_class = sh_class
 
709
        if self.highlighter is not None:
 
710
            # Removing old highlighter
 
711
            # TODO: test if leaving parent/document as is eats memory
 
712
            self.highlighter.setParent(None)
 
713
            self.highlighter.setDocument(None)
 
714
        self.highlighter = self.highlighter_class(self.document(),
 
715
                                                  self.font(),
 
716
                                                  self.color_scheme)
 
717
        self._apply_highlighter_color_scheme()
 
718
 
 
719
    def is_json(self):
 
720
        return self.highlighter_class is sh.JsonSH
660
721
 
661
722
    def is_python(self):
662
723
        return self.highlighter_class is sh.PythonSH
673
734
    def intelligent_tab(self):
674
735
        """Provide intelligent behavoir for Tab key press"""
675
736
        leading_text = self.get_text('sol', 'cursor')
676
 
        if not leading_text.strip(): 
677
 
            # blank line
 
737
        if not leading_text.strip() or leading_text.endswith('#'):
 
738
            # blank line or start of comment
678
739
            self.indent_or_replace()
679
740
        elif self.in_comment_or_string() and not leading_text.endswith(' '):
680
741
            # in a word in a comment
728
789
            self.highlight_current_line()
729
790
        else:
730
791
            self.unhighlight_current_line()
731
 
 
 
792
    
 
793
    def rehighlight_cells(self):
 
794
        """Rehighlight cells when moving the scrollbar"""
 
795
        if self.highlight_current_cell_enabled:
 
796
            self.highlight_current_cell()
732
797
 
733
798
    def setup_margins(self, linenumbers=True, markers=True):
734
799
        """
963
1028
 
964
1029
    def linenumberarea_paint_event(self, event):
965
1030
        """Painting line number area"""
 
1031
        painter = QPainter(self.linenumberarea)
 
1032
        painter.fillRect(event.rect(), self.sideareas_color)
 
1033
        font = painter.font()
966
1034
        font_height = self.fontMetrics().height()
967
 
        painter = QPainter(self.linenumberarea)
968
 
        painter.fillRect(event.rect(), self.area_background_color)
969
1035
 
970
 
        block = self.firstVisibleBlock()
971
 
        block_number = block.blockNumber()
972
 
        top = self.blockBoundingGeometry(block).translated(
973
 
                                                    self.contentOffset()).top()
974
 
        bottom = top + self.blockBoundingRect(block).height()
 
1036
        active_block = self.textCursor().block()
 
1037
        active_line_number = active_block.blockNumber() + 1
975
1038
 
976
1039
        def draw_pixmap(ytop, pixmap):
977
 
            painter.drawPixmap(0, ytop+(font_height-pixmap.height())/2, pixmap)
978
 
        while block.isValid() and top <= event.rect().bottom():
979
 
            if block.isVisible() and bottom >= event.rect().top():
980
 
                line_number = block_number+1
981
 
                painter.setPen(Qt.darkGray)
982
 
                if self.is_cell_separator(block):
983
 
                    painter.setPen(Qt.red)
984
 
                    painter.drawLine(0, top, self.linenumberarea.width(), top)
985
 
                if self.linenumbers_margin:
986
 
                    painter.drawText(0, top, self.linenumberarea.width(),
987
 
                                     font_height, Qt.AlignRight|Qt.AlignBottom,
988
 
                                     to_text_string(line_number))
989
 
                data = block.userData()
990
 
                if self.markers_margin and data:
991
 
                    if data.code_analysis:
992
 
                        for _message, error in data.code_analysis:
993
 
                            if error:
994
 
                                break
 
1040
            painter.drawPixmap(0, ytop + (font_height-pixmap.height()) / 2,
 
1041
                               pixmap)
 
1042
 
 
1043
        for top, line_number, block in self.visible_blocks:
 
1044
            if self.linenumbers_margin:
 
1045
                if line_number == active_line_number:
 
1046
                    font.setWeight(font.Bold)
 
1047
                    painter.setFont(font)
 
1048
                    painter.setPen(self.normal_color)
 
1049
                else:
 
1050
                    font.setWeight(font.Normal)
 
1051
                    painter.setFont(font)
 
1052
                    painter.setPen(self.linenumbers_color)
 
1053
 
 
1054
                painter.drawText(0, top, self.linenumberarea.width(),
 
1055
                                 font_height,
 
1056
                                 Qt.AlignRight | Qt.AlignBottom,
 
1057
                                 to_text_string(line_number))
 
1058
 
 
1059
            data = block.userData()
 
1060
            if self.markers_margin and data:
 
1061
                if data.code_analysis:
 
1062
                    for _message, error in data.code_analysis:
995
1063
                        if error:
996
 
                            draw_pixmap(top, self.error_pixmap)
997
 
                        else:
998
 
                            draw_pixmap(top, self.warning_pixmap)
999
 
                    if data.todo:
1000
 
                        draw_pixmap(top, self.todo_pixmap)
1001
 
                    if data.breakpoint:
1002
 
                        if data.breakpoint_condition is None:
1003
 
                            draw_pixmap(top, self.bp_pixmap)
1004
 
                        else:
1005
 
                            draw_pixmap(top, self.bpc_pixmap)
1006
 
 
1007
 
            block = block.next()
1008
 
            top = bottom
1009
 
            bottom = top + self.blockBoundingRect(block).height()
1010
 
            block_number += 1
 
1064
                            break
 
1065
                    if error:
 
1066
                        draw_pixmap(top, self.error_pixmap)
 
1067
                    else:
 
1068
                        draw_pixmap(top, self.warning_pixmap)
 
1069
                if data.todo:
 
1070
                    draw_pixmap(top, self.todo_pixmap)
 
1071
                if data.breakpoint:
 
1072
                    if data.breakpoint_condition is None:
 
1073
                        draw_pixmap(top, self.bp_pixmap)
 
1074
                    else:
 
1075
                        draw_pixmap(top, self.bpc_pixmap)
1011
1076
 
1012
1077
    def __get_linenumber_from_mouse_event(self, event):
1013
1078
        """Return line number from mouse event"""
1030
1095
        line_number = self.__get_linenumber_from_mouse_event(event)
1031
1096
        block = self.document().findBlockByNumber(line_number-1)
1032
1097
        data = block.userData()
1033
 
        if data and data.code_analysis:
 
1098
 
 
1099
        # this disables pyflakes messages if there is an active drag/selection
 
1100
        # operation
 
1101
        check = self.linenumberarea_released == -1
 
1102
        if data and data.code_analysis and check:
1034
1103
            self.__show_code_analysis_results(line_number, data.code_analysis)
1035
1104
 
 
1105
        if event.buttons() == Qt.LeftButton:
 
1106
            self.linenumberarea_released = line_number
 
1107
            self.linenumberarea_select_lines(self.linenumberarea_pressed,
 
1108
                                             self.linenumberarea_released)
 
1109
 
1036
1110
    def linenumberarea_mousedoubleclick_event(self, event):
1037
1111
        """Handling line number area mouse double-click event"""
1038
1112
        line_number = self.__get_linenumber_from_mouse_event(event)
1039
1113
        shift = event.modifiers() & Qt.ShiftModifier
1040
1114
        self.add_remove_breakpoint(line_number, edit_condition=shift)
1041
1115
 
 
1116
    def linenumberarea_mousepress_event(self, event):
 
1117
        """Handling line number area mouse double press event"""
 
1118
        line_number = self.__get_linenumber_from_mouse_event(event)
 
1119
        self.linenumberarea_pressed = line_number
 
1120
        self.linenumberarea_released = line_number
 
1121
        self.linenumberarea_select_lines(self.linenumberarea_pressed,
 
1122
                                         self.linenumberarea_released)
 
1123
 
 
1124
    def linenumberarea_mouserelease_event(self, event):
 
1125
        """Handling line number area mouse release event"""
 
1126
        self.linenumberarea_released = -1
 
1127
        self.linenumberarea_pressed = -1
 
1128
 
 
1129
    def linenumberarea_select_lines(self, linenumber_pressed,
 
1130
                                    linenumber_released):
 
1131
        """Select line(s) after a mouse press/mouse press drag event"""
 
1132
        find_block_by_line_number = self.document().findBlockByLineNumber
 
1133
        move_n_blocks = (linenumber_released - linenumber_pressed)
 
1134
        start_line = linenumber_pressed
 
1135
        start_block = find_block_by_line_number(start_line - 1)
 
1136
 
 
1137
        cursor = self.textCursor()
 
1138
        cursor.setPosition(start_block.position())
 
1139
 
 
1140
        # Select/drag downwards
 
1141
        if move_n_blocks > 0:
 
1142
            for n in range(abs(move_n_blocks) + 1):
 
1143
                cursor.movePosition(cursor.NextBlock, cursor.KeepAnchor)
 
1144
        # Select/drag upwards or select single line
 
1145
        else:
 
1146
            cursor.movePosition(cursor.NextBlock)
 
1147
            for n in range(abs(move_n_blocks) + 1):
 
1148
                cursor.movePosition(cursor.PreviousBlock, cursor.KeepAnchor)
 
1149
 
 
1150
        # Account for last line case
 
1151
        if linenumber_released == self.blockCount():
 
1152
            cursor.movePosition(cursor.EndOfBlock, cursor.KeepAnchor)
 
1153
        else:
 
1154
            cursor.movePosition(cursor.StartOfBlock, cursor.KeepAnchor)
 
1155
 
 
1156
        self.setTextCursor(cursor)
 
1157
 
1042
1158
    #------Breakpoints
1043
1159
    def add_remove_breakpoint(self, line_number=None, condition=None,
1044
1160
                              edit_condition=False):
1052
1168
        data = block.userData()
1053
1169
        if data:
1054
1170
            data.breakpoint = not data.breakpoint
 
1171
            old_breakpoint_condition = data.breakpoint_condition
1055
1172
            data.breakpoint_condition = None
1056
1173
        else:
1057
1174
            data = BlockUserData(self)
1058
1175
            data.breakpoint = True
 
1176
            old_breakpoint_condition = None
1059
1177
        if condition is not None:
1060
1178
            data.breakpoint_condition = condition
1061
1179
        if edit_condition:
1062
1180
            data.breakpoint = True
1063
1181
            condition = data.breakpoint_condition
1064
 
            if condition is None:
1065
 
                condition = ''
 
1182
            if old_breakpoint_condition is not None:
 
1183
                condition = old_breakpoint_condition
1066
1184
            condition, valid = QInputDialog.getText(self,
1067
1185
                                        _('Breakpoint'),
1068
1186
                                        _("Condition:"),
1073
1191
                    condition = None
1074
1192
                data.breakpoint_condition = condition
1075
1193
            else:
 
1194
                data.breakpoint_condition = old_breakpoint_condition
1076
1195
                return
1077
1196
        if data.breakpoint:
1078
1197
            text = to_text_string(block.text()).strip()
1100
1219
        self.breakpoints = []
1101
1220
        for data in self.blockuserdata_list[:]:
1102
1221
            data.breakpoint = False
1103
 
#            data.breakpoint_condition = None # not necessary, but logical
 
1222
            # data.breakpoint_condition = None  # not necessary, but logical
1104
1223
            if data.is_empty():
1105
1224
                del data
1106
1225
 
1170
1289
        
1171
1290
        # Filling the whole painting area
1172
1291
        painter = QPainter(self.scrollflagarea)
1173
 
        painter.fillRect(event.rect(), self.area_background_color)
 
1292
        painter.fillRect(event.rect(), self.sideareas_color)
1174
1293
        block = self.document().firstBlock()
1175
1294
        
1176
1295
        # Painting warnings and todos
1276
1395
            self.currentcell_color = hl.get_currentcell_color()
1277
1396
            self.occurence_color = hl.get_occurence_color()
1278
1397
            self.ctrl_click_color = hl.get_ctrlclick_color()
1279
 
            self.area_background_color = hl.get_sideareas_color()
 
1398
            self.sideareas_color = hl.get_sideareas_color()
 
1399
            self.comment_color = hl.get_comment_color()
 
1400
            self.normal_color = hl.get_foreground_color()
1280
1401
            self.matched_p_color = hl.get_matched_p_color()
1281
1402
            self.unmatched_p_color = hl.get_unmatched_p_color()
1282
1403
 
1321
1442
        """Set the text of the editor"""
1322
1443
        self.setPlainText(text)
1323
1444
        self.set_eol_chars(text)
1324
 
#        if self.supported_language:
1325
 
#            self.highlighter.rehighlight()
 
1445
        #if self.supported_language:
 
1446
            #self.highlighter.rehighlight()
1326
1447
 
1327
1448
    def set_text_from_file(self, filename, language=None):
1328
1449
        """Set the text of the editor from file *fname*"""
1718
1839
            cursor.endEditBlock()
1719
1840
            return True
1720
1841
 
 
1842
    def clear_all_output(self):
 
1843
        """removes all ouput in the ipynb format (Json only)"""
 
1844
        if self.is_json() and nbformat is not None:
 
1845
            nb = nbformat.current.reads(self.toPlainText(), 'json')
 
1846
            if nb.worksheets:
 
1847
                for cell in nb.worksheets[0].cells:
 
1848
                    if 'outputs' in cell:
 
1849
                        cell['outputs'] = []
 
1850
                    if 'prompt_number' in cell:
 
1851
                        cell['prompt_number'] = None
 
1852
            # We do the following rather than using self.setPlainText
 
1853
            # to benefit from QTextEdit's undo/redo feature. 
 
1854
            self.selectAll()
 
1855
            self.insertPlainText(nbformat.current.writes(nb, 'json'))
 
1856
        else:
 
1857
            return
 
1858
 
 
1859
    sig_new_file = Signal(str)
 
1860
 
 
1861
    def convert_notebook(self):
 
1862
        """Convert an IPython notebook to a Python script in editor"""
 
1863
        if nbformat is not None:
 
1864
            nb = nbformat.current.reads(self.toPlainText(), 'json')
 
1865
            # Use writes_py if nbconvert is not available
 
1866
            if nbexporter is None:
 
1867
                script = nbformat.current.writes_py(nb)
 
1868
            else:
 
1869
                script = nbexporter().from_notebook_node(nb)[0]
 
1870
            self.sig_new_file.emit(script)
 
1871
 
1721
1872
    def indent(self, force=False):
1722
1873
        """
1723
1874
        Indent current line or selection
2099
2250
                           _("Comment")+"/"+_("Uncomment"),
2100
2251
                           icon=get_icon("comment.png"),
2101
2252
                           triggered=self.toggle_comment)
 
2253
        self.clear_all_output_action = create_action(self,
 
2254
                           _("Clear all ouput"), icon='ipython_console.png',
 
2255
                           triggered=self.clear_all_output)
 
2256
        self.ipynb_convert_action = create_action(self, _("Convert to Python script"),
 
2257
                           triggered=self.convert_notebook, icon='python.png')
2102
2258
        self.gotodef_action = create_action(self, _("Go to definition"),
2103
2259
                                   triggered=self.go_to_definition_from_cursor)
2104
 
        run_selection_action = create_action(self,
 
2260
        self.run_selection_action = create_action(self,
2105
2261
                        _("Run &selection or current line"),
2106
2262
                        icon='run_selection.png',
2107
2263
                        triggered=lambda: self.emit(SIGNAL('run_selection()')))
2112
2268
                      QKeySequence(QKeySequence.ZoomOut), icon='zoom_out.png',
2113
2269
                      triggered=lambda: self.emit(SIGNAL('zoom_out()')))
2114
2270
        self.menu = QMenu(self)
2115
 
        add_actions(self.menu, (self.undo_action, self.redo_action, None,
2116
 
                                self.cut_action, self.copy_action,
2117
 
                                paste_action, self.delete_action,
2118
 
                                None, selectall_action, None, zoom_in_action,
2119
 
                                zoom_out_action, None, toggle_comment_action,
2120
 
                                None, run_selection_action,
2121
 
                                self.gotodef_action))
 
2271
        if nbformat is not None:
 
2272
            add_actions(self.menu, (self.undo_action, self.redo_action, None,
 
2273
                                    self.cut_action, self.copy_action,
 
2274
                                    paste_action, self.delete_action,
 
2275
                                    None, self.clear_all_output_action,
 
2276
                                    self.ipynb_convert_action, None, 
 
2277
                                    selectall_action, None, zoom_in_action,
 
2278
                                    zoom_out_action, None, toggle_comment_action,
 
2279
                                    None, self.run_selection_action,
 
2280
                                    self.gotodef_action))
 
2281
        else:
 
2282
            add_actions(self.menu, (self.undo_action, self.redo_action, None,
 
2283
                                    self.cut_action, self.copy_action,
 
2284
                                    paste_action, self.delete_action,
 
2285
                                    None, selectall_action, None, zoom_in_action,
 
2286
                                    zoom_out_action, None, toggle_comment_action,
 
2287
                                    None, self.run_selection_action,
 
2288
                                    self.gotodef_action))
 
2289
 
2122
2290
            
2123
2291
        # Read-only context-menu
2124
2292
        self.readonly_menu = QMenu(self)
2125
2293
        add_actions(self.readonly_menu,
2126
2294
                    (self.copy_action, None, selectall_action,
2127
2295
                     self.gotodef_action))
2128
 
    
 
2296
 
2129
2297
    def keyPressEvent(self, event):
2130
2298
        """Reimplement Qt method"""
2131
2299
        key = event.key()
2348
2516
 
2349
2517
    def contextMenuEvent(self, event):
2350
2518
        """Reimplement Qt method"""
2351
 
        state = self.has_selected_text()
2352
 
        self.copy_action.setEnabled(state)
2353
 
        self.cut_action.setEnabled(state)
2354
 
        self.delete_action.setEnabled(state)
2355
 
        self.undo_action.setEnabled( self.document().isUndoAvailable() )
2356
 
        self.redo_action.setEnabled( self.document().isRedoAvailable() )
 
2519
        nonempty_selection = self.has_selected_text()
 
2520
        self.copy_action.setEnabled(nonempty_selection)
 
2521
        self.cut_action.setEnabled(nonempty_selection)
 
2522
        self.delete_action.setEnabled(nonempty_selection)
 
2523
        self.clear_all_output_action.setVisible(self.is_json())
 
2524
        self.ipynb_convert_action.setVisible(self.is_json())
 
2525
        self.run_selection_action.setEnabled(nonempty_selection)
 
2526
        self.run_selection_action.setVisible(self.is_python())
 
2527
        self.gotodef_action.setVisible(self.go_to_definition_enabled\
 
2528
                                       and self.is_python_like())        
 
2529
        
 
2530
        # Code duplication go_to_definition_from_cursor and mouse_move_event
 
2531
        cursor = self.textCursor()
 
2532
        text = to_text_string(cursor.selectedText())
 
2533
        if len(text) == 0:
 
2534
            cursor.select(QTextCursor.WordUnderCursor)
 
2535
            text = to_text_string(cursor.selectedText())
 
2536
        self.gotodef_action.setEnabled(sourcecode.is_keyword(text)) 
 
2537
           
 
2538
        self.undo_action.setEnabled( self.document().isUndoAvailable())
 
2539
        self.redo_action.setEnabled( self.document().isRedoAvailable())
2357
2540
        menu = self.menu
2358
2541
        if self.isReadOnly():
2359
2542
            menu = self.readonly_menu
2379
2562
        else:
2380
2563
            TextEditBaseWidget.dropEvent(self, event)
2381
2564
 
 
2565
    #------ Paint event
 
2566
    def paintEvent(self, event):
 
2567
        """Overrides paint event to update the list of visible blocks"""
 
2568
        self.update_visible_blocks(event)
 
2569
        TextEditBaseWidget.paintEvent(self, event)
 
2570
        self.painted.emit(event)
 
2571
 
 
2572
    def update_visible_blocks(self, event):
 
2573
        """Update the list of visible blocks/lines position"""
 
2574
        self.__visible_blocks[:] = []
 
2575
        block = self.firstVisibleBlock()
 
2576
        blockNumber = block.blockNumber()
 
2577
        top = int(self.blockBoundingGeometry(block).translated(
 
2578
            self.contentOffset()).top())
 
2579
        bottom = top + int(self.blockBoundingRect(block).height())
 
2580
        ebottom_top = 0
 
2581
        ebottom_bottom = self.height()
 
2582
 
 
2583
        while block.isValid():
 
2584
            visible = (top >= ebottom_top and bottom <= ebottom_bottom)
 
2585
            if not visible:
 
2586
                break
 
2587
            if block.isVisible():
 
2588
                self.__visible_blocks.append((top, blockNumber+1, block))
 
2589
            block = block.next()
 
2590
            top = bottom
 
2591
            bottom = top + int(self.blockBoundingRect(block).height())
 
2592
            blockNumber = block.blockNumber()
 
2593
 
 
2594
    def _draw_editor_cell_divider(self):
 
2595
        """Draw a line on top of a define cell"""
 
2596
        if self.supported_cell_language:
 
2597
            cell_line_color = self.comment_color
 
2598
            painter = QPainter(self.viewport())
 
2599
            pen = painter.pen()
 
2600
            pen.setStyle(Qt.SolidLine)
 
2601
            pen.setBrush(cell_line_color)
 
2602
            painter.setPen(pen)
 
2603
    
 
2604
            for top, line_number, block in self.visible_blocks:
 
2605
                if self.is_cell_separator(block):
 
2606
                    painter.drawLine(4, top, self.width(), top)
 
2607
 
 
2608
    @property
 
2609
    def visible_blocks(self):
 
2610
        """
 
2611
        Returns the list of visible blocks.
 
2612
 
 
2613
        Each element in the list is a tuple made up of the line top position,
 
2614
        the line number (already 1 based), and the QTextBlock itself.
 
2615
 
 
2616
        :return: A list of tuple(top position, line number, block)
 
2617
        :rtype: List of tuple(int, int, QtGui.QTextBlock)
 
2618
        """
 
2619
        return self.__visible_blocks
2382
2620
 
2383
2621
#===============================================================================
2384
2622
# CodeEditor's Printer