Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
0cfc208
SpittableCodeEditTabWidget: also avoid ambiguous names when a name co…
smathot Aug 17, 2019
b07f0be
Fix race condition where None is passed to TextHelper.line_text()
smathot Sep 2, 2019
fd56769
CodeEdit: remember if the code edit was already closed
smathot Sep 7, 2019
cdd8b26
CodeEdit: don't unnecessarily call _reset_stylesheet()
smathot Sep 8, 2019
e8dd60f
SplittableCodeEditTabWidget: set mime type on CodeEdit widgets that u…
smathot Sep 12, 2019
a241bd8
PygmentsSH: don't try to set unspecified mime type
smathot Sep 12, 2019
ef19362
PygmentsSH: fall back to TextLexer
smathot Sep 14, 2019
df18fb8
SplittableCodeEditTabWidget: only rename tabs when an actual save-as …
smathot Sep 16, 2019
6a8e031
SyntaxHighlighter: don't unnecessarily call _reset_stylesheet()
smathot Sep 20, 2019
804204a
Fall back to text/plain mimetype when lexer is missing
smathot Nov 6, 2019
506741b
CodeEdit: home jumps to beginning of line (not block) for unindented …
smathot Nov 8, 2019
139b4bc
CodeEdit: correctly update visible blocks when first block is partly …
smathot Nov 8, 2019
c9540dc
PanelsManager: fix references to modes
smathot Nov 15, 2019
7a57f08
PygmentsSH: copy lexer when cloning
smathot Nov 21, 2019
ee0803f
CodeEdit: don't assume that clones have exactly the same modes and pa…
smathot Nov 21, 2019
135da1c
CodeEdit: better implementation of cut
smathot Nov 24, 2019
d58bc1f
CodeEdit: fix swapping multiline blocks
smathot Nov 24, 2019
3998474
AutoCompleteMode: make list of characters that shoudn't be duplicated…
smathot Nov 24, 2019
90f124c
SearchAndReplacePanel: postpone selecting until searching is done
smathot Nov 29, 2019
64eaf17
Remove line (don't just blank) when cutting unselected text
smathot Dec 13, 2019
cfa583e
Remove extraneous print statements
smathot Dec 14, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 79 additions & 37 deletions pyqode/core/api/code_edit.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,8 @@ def font_name(self, value):
if value == "":
value = self._DEFAULT_FONT
self._font_family = value
self._reset_stylesheet()
if self._auto_reset_stylesheet:
self._reset_stylesheet()
for c in self.clones:
c.font_name = value

Expand Down Expand Up @@ -217,7 +218,8 @@ def font_size(self):
@font_size.setter
def font_size(self, value):
self._font_size = value
self._reset_stylesheet()
if self._auto_reset_stylesheet:
self._reset_stylesheet()
for c in self.clones:
c.font_size = value

Expand All @@ -231,7 +233,8 @@ def background(self):
@background.setter
def background(self, value):
self._background = value
self._reset_stylesheet()
if self._auto_reset_stylesheet:
self._reset_stylesheet()
for c in self.clones:
c.background = value

Expand All @@ -245,7 +248,8 @@ def foreground(self):
@foreground.setter
def foreground(self, value):
self._foreground = value
self._reset_stylesheet()
if self._auto_reset_stylesheet:
self._reset_stylesheet()
for c in self.clones:
c.foreground = value

Expand Down Expand Up @@ -275,7 +279,8 @@ def selection_background(self):
@selection_background.setter
def selection_background(self, value):
self._sel_background = value
self._reset_stylesheet()
if self._auto_reset_stylesheet:
self._reset_stylesheet()
for c in self.clones:
c.selection_background = value

Expand Down Expand Up @@ -434,8 +439,10 @@ def __init__(self, parent=None, create_default_actions=True):
:attr:`show_menu_enabled` to False.
"""
super(CodeEdit, self).__init__(parent)
self._auto_reset_stylesheet = False
self.installEventFilter(self)
self.clones = []
self._closed = False
self._show_ctx_mnu = True
self._default_font_size = 10
self._backend = BackendManager(self)
Expand Down Expand Up @@ -502,6 +509,8 @@ def __init__(self, parent=None, create_default_actions=True):
self.setCenterOnScroll(True)
self.setLineWrapMode(self.NoWrap)
self.setCursorWidth(2)
self._auto_reset_stylesheet = True
self._reset_stylesheet()

def __repr__(self):
return '%s(path=%r)' % (self.__class__.__name__, self.file.path)
Expand Down Expand Up @@ -548,15 +557,22 @@ def link(self, clone):
clone.file._encoding = self.file.encoding
clone.file._mimetype = self.file.mimetype
clone.setDocument(self.document())
for original_mode, mode in zip(list(self.modes), list(clone.modes)):
mode.enabled = original_mode.enabled
mode.clone_settings(original_mode)
for original_panel, panel in zip(
list(self.panels), list(clone.panels)):
panel.enabled = original_panel.isEnabled()
panel.clone_settings(original_panel)
for original_mode in self.modes:
try:
clone_mode = clone.modes.get(original_mode.name)
except KeyError:
continue
clone_mode.enabled = original_mode.enabled
clone_mode.clone_settings(original_mode)
for original_panel in self.panels:
try:
clone_panel = clone.panels.get(original_panel.name)
except KeyError:
continue
clone_panel.enabled = original_panel.isEnabled()
clone_panel.clone_settings(original_panel)
if not original_panel.isVisible():
panel.setVisible(False)
clone_panel.setVisible(False)
clone.use_spaces_instead_of_tabs = self.use_spaces_instead_of_tabs
clone.tab_length = self.tab_length
clone._save_on_focus_out = self._save_on_focus_out
Expand All @@ -581,6 +597,9 @@ def close(self, clear=True):

:param clear: True to clear the editor content before closing.
"""
if self._closed:
return
self._closed = True
if self._tooltips_runner:
self._tooltips_runner.cancel_requests()
self._tooltips_runner = None
Expand Down Expand Up @@ -875,25 +894,29 @@ def eventFilter(self, obj, event):

def cut(self):
"""
Cuts the selected text or the whole line if no text was selected.
Cuts the selected text or the whole line if no text was selected. When
cutting a full line that consists of only whitespace, the line is only
deleted, to avoid overwriting the clipboard with whitespace.
"""

tc = self.textCursor()
helper = TextHelper(self)
tc.beginEditBlock()
no_selection = False
sText = tc.selection().toPlainText()
# If only whitespace is selected, simply delete it
if not helper.current_line_text() and not sText.strip():
tc.deleteChar()
if not tc.hasSelection():
tc.movePosition(tc.StartOfLine)
tc.movePosition(tc.Left)
tc.movePosition(tc.Right, tc.KeepAnchor)
tc.movePosition(tc.EndOfLine, tc.KeepAnchor)
from_selection = False
else:
from_selection = True
if from_selection or tc.selectedText().strip():
need_cut = True
else:
if not self.textCursor().hasSelection():
no_selection = True
TextHelper(self).select_whole_line()
super(CodeEdit, self).cut()
if no_selection:
tc.deleteChar()
tc.removeSelectedText()
need_cut = False
tc.endEditBlock()
self.setTextCursor(tc)
super(CodeEdit, self).cut()

def copy(self):
"""
Expand Down Expand Up @@ -929,7 +952,7 @@ def __swapLine(self, up):
return
# Select the current lines and the line that will be swapped, turn
# them into a list, and then perform the swap on this list
helper.select_lines(start_index, end_index)
helper.select_lines(start_index, end_index, select_blocks=True)
lines = helper.selected_text().replace(u'\u2029', u'\n').split(u'\n')
if up:
lines = lines[1:] + [lines[0]]
Expand All @@ -944,9 +967,11 @@ def __swapLine(self, up):
if has_selection:
# If text was originally selected, select the range again
if up:
helper.select_lines(start_index, end_index - 1)
helper.select_lines(start_index, end_index - 1,
select_blocks=True)
else:
helper.select_lines(start_index + 1, end_index)
helper.select_lines(start_index + 1, end_index,
select_blocks=True)
else:
# Else restore cursor position, while moving with the swap
helper.goto_line(line - 1 if up else line + 1, column)
Expand Down Expand Up @@ -1316,11 +1341,13 @@ def _update_visible_blocks(self, *args):
bottom = top + int(self.blockBoundingRect(block).height())
ebottom_top = 0
ebottom_bottom = self.height()
first_block = True
while block.isValid():
visible = (top >= ebottom_top and bottom <= ebottom_bottom)
if not visible:
if not visible and not first_block:
break
if block.isVisible():
first_block = False
if visible and block.isVisible():
self._visible_blocks.append((top, block_nbr, block))
block = block.next()
top = bottom
Expand All @@ -1333,8 +1360,11 @@ def _on_text_changed(self):
ln = TextHelper(self).cursor_position()[0]
self._modified_lines.add(ln)


def _reset_stylesheet(self):
""" Resets stylesheet"""
# This function is called very often during initialization, which
# impacts performance. This is a hack to avoid this.
self.setFont(QtGui.QFont(self._font_family,
self._font_size + self._zoom_level))
flg_stylesheet = hasattr(self, '_flg_stylesheet')
Expand Down Expand Up @@ -1373,17 +1403,29 @@ def _reset_stylesheet(self):

def _do_home_key(self, event=None, select=False):
""" Performs home key action """
# get nb char to first significative char
delta = (self.textCursor().positionInBlock() -
TextHelper(self).line_indent())
cursor = self.textCursor()
move = QtGui.QTextCursor.MoveAnchor
if select:
move = QtGui.QTextCursor.KeepAnchor
if delta > 0:
cursor.movePosition(QtGui.QTextCursor.Left, move, delta)
indent = TextHelper(self).line_indent()
# Scenario 1: We're on an unindented block. In that case, we jump back
# to the start of the visible line, but not all the way to the back of
# the block. This is what you would expect when working with text and
# line wrapping is enabled.
if not indent:
cursor.movePosition(QtGui.QTextCursor.StartOfLine, move)
else:
cursor.movePosition(QtGui.QTextCursor.StartOfBlock, move)
delta = self.textCursor().positionInBlock() - indent
# Scenario 2: We're on an indented block. In that case, we move
# back to the indented position. This is what you would expect when
# working with code.
if delta > 0:
cursor.movePosition(QtGui.QTextCursor.Left, move, delta)
# Scenario 3: We're on an indented block, but we're already at the
# start of the indentation. In that case, we jump back to the
# beginning of the block.
else:
cursor.movePosition(QtGui.QTextCursor.StartOfBlock, move)
self.setTextCursor(cursor)
if event:
event.accept()
Expand Down
2 changes: 2 additions & 0 deletions pyqode/core/api/syntax_highlighter.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ def refresh_editor(self, color_scheme):

:param color_scheme: new color scheme.
"""
self.editor._auto_reset_stylesheet = False
self.editor.background = color_scheme.background
self.editor.foreground = color_scheme.formats[
'normal'].foreground().color()
Expand All @@ -272,6 +273,7 @@ def refresh_editor(self, color_scheme):
pass
else:
mode.refresh_decorations(force=True)
self.editor._auto_reset_stylesheet = True
self.editor._reset_stylesheet()

def __init__(self, parent, color_scheme=None):
Expand Down
33 changes: 26 additions & 7 deletions pyqode/core/api/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,12 @@ def line_text(self, line_nbr):
:return: Entire line's text
:rtype: str
"""

# Under some (apparent) race conditions, this function can be called
# with a None line number. This should be fixed in a better way, but
# for now we return an empty string to avoid crashes.
if line_nbr is None:
return ''
doc = self._editor.document()
block = doc.findBlockByNumber(line_nbr)
return block.text()
Expand Down Expand Up @@ -432,7 +438,8 @@ def move_cursor_to(self, line):
cursor.setPosition(block.position())
return cursor

def select_lines(self, start=0, end=-1, apply_selection=True):
def select_lines(self, start=0, end=-1, apply_selection=True,
select_blocks=False):
"""
Selects entire lines between start and end line numbers.

Expand All @@ -447,6 +454,8 @@ def select_lines(self, start=0, end=-1, apply_selection=True):
end of the document
:param apply_selection: True to apply the selection before returning
the QTextCursor.
:param select_blocks: True to operate on blocks rather than visual
lines.
:returns: A QTextCursor that holds the requested selection
"""
editor = self._editor
Expand All @@ -455,21 +464,31 @@ def select_lines(self, start=0, end=-1, apply_selection=True):
if start < 0:
start = 0
text_cursor = self.move_cursor_to(start)
if select_blocks:
move_start = text_cursor.StartOfBlock
move_end = text_cursor.EndOfBlock
move_up = text_cursor.PreviousBlock
move_down = text_cursor.NextBlock
else:
move_start = text_cursor.StartOfLine
move_end = text_cursor.EndOfLine
move_up = text_cursor.Up
move_down = text_cursor.Down
if end > start: # Going down
text_cursor.movePosition(text_cursor.Down,
text_cursor.movePosition(move_down,
text_cursor.KeepAnchor, end - start)
text_cursor.movePosition(text_cursor.EndOfLine,
text_cursor.movePosition(move_end,
text_cursor.KeepAnchor)
elif end < start: # going up
# don't miss end of line !
text_cursor.movePosition(text_cursor.EndOfLine,
text_cursor.movePosition(move_end,
text_cursor.MoveAnchor)
text_cursor.movePosition(text_cursor.Up,
text_cursor.movePosition(move_up,
text_cursor.KeepAnchor, start - end)
text_cursor.movePosition(text_cursor.StartOfLine,
text_cursor.movePosition(move_start,
text_cursor.KeepAnchor)
else:
text_cursor.movePosition(text_cursor.EndOfLine,
text_cursor.movePosition(move_end,
text_cursor.KeepAnchor)
if apply_selection:
editor.setTextCursor(text_cursor)
Expand Down
4 changes: 2 additions & 2 deletions pyqode/core/managers/panels.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,13 +109,13 @@ def keys(self):
"""
Returns the list of installed panel names.
"""
return self._modes.keys()
return self._panels.keys()

def values(self):
"""
Returns the list of installed panels.
"""
return self._modes.values()
return self._panels.values()

def __iter__(self):
lst = []
Expand Down
15 changes: 7 additions & 8 deletions pyqode/core/modes/autocomplete.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ def __init__(self):
super(AutoCompleteMode, self).__init__()
#: Auto complete mapping, maps input key with completion text.
self.MAPPING = {'"': '"', "'": "'", "(": ")", "{": "}", "[": "]"}
self.AVOID_DUPLICATES = ')', ']', '}'
#: The format to use for each symbol in mapping when there is a selection
self.SELECTED_QUOTES_FORMATS = {key: '%s%s%s' for key in self.MAPPING.keys()}
#: The format to use for each symbol in mapping when there is no selection
Expand Down Expand Up @@ -92,15 +93,13 @@ def _on_key_pressed(self, event):
tc.endEditBlock()
self.editor.setTextCursor(tc)
ignore = True
elif txt and next_char == txt and next_char in self.MAPPING:
elif (
txt and next_char == txt and (
next_char in self.MAPPING or
txt in self.AVOID_DUPLICATES
)
):
ignore = True
elif event.text() == ')' or event.text() == ']' or event.text() == '}':
# if typing the same symbol twice, the symbol should not be written
# and the cursor moved just after the char
# e.g. if you type ) just before ), the cursor will just move after
# the existing )
if next_char == event.text():
ignore = True
if ignore:
event.accept()
TextHelper(self.editor).clear_selection()
Expand Down
Loading