170 lines
6.1 KiB
Python
170 lines
6.1 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
Contains the default indenter.
|
|
"""
|
|
import logging
|
|
from pyqode.core.api import TextHelper
|
|
from pyqode.core.api.mode import Mode
|
|
from pyqode.qt import QtGui
|
|
|
|
|
|
def _logger():
|
|
return logging.getLogger(__name__)
|
|
|
|
|
|
class IndenterMode(Mode):
|
|
""" Implements classic indentation/tabulation (Tab/Shift+Tab)
|
|
|
|
It inserts/removes tabulations (a series of spaces defined by the
|
|
tabLength settings) at the cursor position if there is no selection,
|
|
otherwise it fully indents/un-indents selected lines.
|
|
|
|
To trigger an indentation/un-indentation programatically, you must emit
|
|
:attr:`pyqode.core.api.CodeEdit.indent_requested` or
|
|
:attr:`pyqode.core.api.CodeEdit.unindent_requested`.
|
|
"""
|
|
def __init__(self):
|
|
super(IndenterMode, self).__init__()
|
|
|
|
def on_state_changed(self, state):
|
|
if state:
|
|
self.editor.indent_requested.connect(self.indent)
|
|
self.editor.unindent_requested.connect(self.unindent)
|
|
else:
|
|
self.editor.indent_requested.disconnect(self.indent)
|
|
self.editor.unindent_requested.disconnect(self.unindent)
|
|
|
|
def indent_selection(self, cursor):
|
|
"""
|
|
Indent selected text
|
|
|
|
:param cursor: QTextCursor
|
|
"""
|
|
doc = self.editor.document()
|
|
tab_len = self.editor.tab_length
|
|
cursor.beginEditBlock()
|
|
nb_lines = len(cursor.selection().toPlainText().splitlines())
|
|
c = self.editor.textCursor()
|
|
if c.atBlockStart() and c.position() == c.selectionEnd():
|
|
nb_lines += 1
|
|
block = doc.findBlock(cursor.selectionStart())
|
|
i = 0
|
|
# indent every lines
|
|
while i < nb_lines:
|
|
nb_space_to_add = tab_len
|
|
cursor = QtGui.QTextCursor(block)
|
|
cursor.movePosition(cursor.StartOfLine, cursor.MoveAnchor)
|
|
if self.editor.use_spaces_instead_of_tabs:
|
|
for _ in range(nb_space_to_add):
|
|
cursor.insertText(" ")
|
|
else:
|
|
cursor.insertText('\t')
|
|
block = block.next()
|
|
i += 1
|
|
cursor.endEditBlock()
|
|
|
|
def unindent_selection(self, cursor):
|
|
"""
|
|
Un-indents selected text
|
|
|
|
:param cursor: QTextCursor
|
|
"""
|
|
doc = self.editor.document()
|
|
tab_len = self.editor.tab_length
|
|
nb_lines = len(cursor.selection().toPlainText().splitlines())
|
|
if nb_lines == 0:
|
|
nb_lines = 1
|
|
block = doc.findBlock(cursor.selectionStart())
|
|
assert isinstance(block, QtGui.QTextBlock)
|
|
i = 0
|
|
_logger().debug('unindent selection: %d lines', nb_lines)
|
|
while i < nb_lines:
|
|
txt = block.text()
|
|
_logger().debug('line to unindent: %s', txt)
|
|
_logger().debug('self.editor.use_spaces_instead_of_tabs: %r',
|
|
self.editor.use_spaces_instead_of_tabs)
|
|
if self.editor.use_spaces_instead_of_tabs:
|
|
indentation = (len(txt) - len(txt.lstrip()))
|
|
else:
|
|
indentation = len(txt) - len(txt.replace('\t', ''))
|
|
_logger().debug('unindent line %d: %d spaces', i, indentation)
|
|
if indentation > 0:
|
|
c = QtGui.QTextCursor(block)
|
|
c.movePosition(c.StartOfLine, cursor.MoveAnchor)
|
|
for _ in range(tab_len):
|
|
txt = block.text()
|
|
if len(txt) and txt[0] == ' ':
|
|
c.deleteChar()
|
|
block = block.next()
|
|
i += 1
|
|
return cursor
|
|
|
|
def indent(self):
|
|
"""
|
|
Indents text at cursor position.
|
|
"""
|
|
cursor = self.editor.textCursor()
|
|
assert isinstance(cursor, QtGui.QTextCursor)
|
|
if cursor.hasSelection():
|
|
self.indent_selection(cursor)
|
|
else:
|
|
# simply insert indentation at the cursor position
|
|
tab_len = self.editor.tab_length
|
|
cursor.beginEditBlock()
|
|
if self.editor.use_spaces_instead_of_tabs:
|
|
nb_space_to_add = tab_len - cursor.positionInBlock() % tab_len
|
|
cursor.insertText(nb_space_to_add * " ")
|
|
else:
|
|
cursor.insertText('\t')
|
|
cursor.endEditBlock()
|
|
|
|
def count_deletable_spaces(self, cursor, max_spaces):
|
|
# count the number of spaces deletable, stop at tab len
|
|
max_spaces = abs(max_spaces)
|
|
if max_spaces > self.editor.tab_length:
|
|
max_spaces = self.editor.tab_length
|
|
spaces = 0
|
|
trav_cursor = QtGui.QTextCursor(cursor)
|
|
while spaces < max_spaces or trav_cursor.atBlockStart():
|
|
pos = trav_cursor.position()
|
|
trav_cursor.movePosition(cursor.Left, cursor.KeepAnchor)
|
|
char = trav_cursor.selectedText()
|
|
if char == " ":
|
|
spaces += 1
|
|
else:
|
|
break
|
|
trav_cursor.setPosition(pos - 1)
|
|
return spaces
|
|
|
|
def unindent(self):
|
|
"""
|
|
Un-indents text at cursor position.
|
|
"""
|
|
|
|
_logger().debug('unindent')
|
|
cursor = self.editor.textCursor()
|
|
_logger().debug('cursor has selection %r', cursor.hasSelection())
|
|
if cursor.hasSelection():
|
|
cursor.beginEditBlock()
|
|
self.unindent_selection(cursor)
|
|
cursor.endEditBlock()
|
|
self.editor.setTextCursor(cursor)
|
|
else:
|
|
tab_len = self.editor.tab_length
|
|
indentation = cursor.positionInBlock()
|
|
max_spaces = tab_len - (indentation - (indentation % tab_len))
|
|
spaces = self.count_deletable_spaces(cursor, max_spaces)
|
|
_logger().debug('deleting %d space before cursor' % spaces)
|
|
cursor.beginEditBlock()
|
|
if spaces:
|
|
# delete spaces before cursor
|
|
for _ in range(spaces):
|
|
cursor.deletePreviousChar()
|
|
else:
|
|
# un-indent whole line
|
|
_logger().debug('un-indent whole line')
|
|
cursor = self.unindent_selection(cursor)
|
|
cursor.endEditBlock()
|
|
self.editor.setTextCursor(cursor)
|
|
_logger().debug(cursor.block().text())
|