Edit Book: Implement auto-indent, now when pressing the Enter key to start a new line, the new line's indentation is automatically created

This commit is contained in:
Kovid Goyal 2014-11-25 10:14:02 +05:30
parent 9575a482a7
commit 690b84ce12
5 changed files with 137 additions and 10 deletions

View File

@ -6,7 +6,12 @@ from __future__ import (unicode_literals, division, absolute_import,
__license__ = 'GPL v3'
__copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
from PyQt5.Qt import Qt
from calibre.gui2.tweak_book.editor.smarts import NullSmarts
from calibre.gui2.tweak_book.editor.smarts.utils import (
no_modifiers, get_leading_whitespace_on_block, get_text_before_cursor,
smart_home, smart_backspace, smart_tab, expand_tabs)
def find_rule(raw, rule_address):
import tinycss
@ -27,5 +32,37 @@ def find_rule(raw, rule_address):
return ans
class Smarts(NullSmarts):
pass
def handle_key_press(self, ev, editor):
key = ev.key()
if key in (Qt.Key_Enter, Qt.Key_Return) and no_modifiers(ev, Qt.ControlModifier, Qt.AltModifier):
ls = get_leading_whitespace_on_block(editor)
cursor, text = get_text_before_cursor(editor)
if text.rstrip().endswith('{'):
ls += ' ' * editor.tw
editor.textCursor().insertText('\n' + ls)
return True
if key == Qt.Key_BraceRight:
ls = get_leading_whitespace_on_block(editor)
pls = get_leading_whitespace_on_block(editor, previous=True)
cursor, text = get_text_before_cursor(editor)
if not text.rstrip() and ls >= pls and len(text) > 1:
text = expand_tabs(text, editor.tw)[:-editor.tw]
cursor.insertText(text + '}')
editor.setTextCursor(cursor)
return True
if key == Qt.Key_Home and smart_home(editor, ev):
return True
if key == Qt.Key_Tab and smart_tab(editor, ev):
return True
if key == Qt.Key_Backspace and smart_backspace(editor, ev):
return True
return False

View File

@ -18,6 +18,9 @@ from calibre.gui2.tweak_book.editor.syntax.html import ATTR_NAME, ATTR_END, ATTR
from calibre.utils.icu import utf16_length
from calibre.gui2.tweak_book import tprefs
from calibre.gui2.tweak_book.editor.smarts import NullSmarts
from calibre.gui2.tweak_book.editor.smarts.utils import (
no_modifiers, get_leading_whitespace_on_block, get_text_before_cursor,
get_text_after_cursor, smart_home, smart_backspace, smart_tab, expand_tabs)
get_offset = itemgetter(0)
PARAGRAPH_SEPARATOR = '\u2029'
@ -274,6 +277,12 @@ entity_pat = re.compile(r'&(#{0,1}[a-zA-Z0-9]{1,8});$')
class Smarts(NullSmarts):
def __init__(self, *args, **kwargs):
if not hasattr(Smarts, 'regexps_compiled'):
Smarts.regexps_compiled = True
Smarts.tag_pat = re.compile(r'<[^>]+>')
Smarts.closing_tag_pat = re.compile(r'<\s*/[^>]+>')
Smarts.closing_pat = re.compile(r'<\s*/')
Smarts.self_closing_pat = re.compile(r'/\s*>')
NullSmarts.__init__(self, *args, **kwargs)
self.last_matched_tag = None
@ -533,11 +542,55 @@ class Smarts(NullSmarts):
set_style_property(tag, 'text-align', value, editor)
def handle_key_press(self, ev, editor):
text = ev.text()
ev_text = ev.text()
key = ev.key()
if tprefs['replace_entities_as_typed'] and (key == Qt.Key_Semicolon or ';' in text):
is_xml = editor.syntax == 'xml'
if tprefs['replace_entities_as_typed'] and (key == Qt.Key_Semicolon or ';' in ev_text):
self.replace_possible_entity(editor)
return True
if key in (Qt.Key_Enter, Qt.Key_Return) and no_modifiers(ev, Qt.ControlModifier, Qt.AltModifier):
ls = get_leading_whitespace_on_block(editor)
if ls == ' ':
ls = '' # Do not consider a single leading space as indentation
if is_xml:
count = 0
for m in self.tag_pat.finditer(get_text_before_cursor(editor)[1]):
text = m.group()
if self.closing_pat.search(text) is not None:
count -= 1
elif self.self_closing_pat.search(text) is None:
count += 1
if self.closing_tag_pat.match(get_text_after_cursor(editor)[1].lstrip()):
count -= 1
if count > 0:
ls += editor.tw * ' '
editor.textCursor().insertText('\n' + ls)
return True
if key == Qt.Key_Slash:
cursor, text = get_text_before_cursor(editor)
if not text.rstrip().endswith('<'):
return False
text = expand_tabs(text.rstrip()[:-1], editor.tw)
pls = get_leading_whitespace_on_block(editor, previous=True)
if is_xml and not text.lstrip() and len(text) > 1 and len(text) >= len(pls):
# Auto-dedent
text = text[:-editor.tw] + '</'
cursor.insertText(text)
editor.setTextCursor(cursor)
return True
if key == Qt.Key_Home and smart_home(editor, ev):
return True
if key == Qt.Key_Tab and smart_tab(editor, ev):
return True
if key == Qt.Key_Backspace and smart_backspace(editor, ev):
return True
return False
def replace_possible_entity(self, editor):
@ -553,3 +606,36 @@ class Smarts(NullSmarts):
c.setPosition(c.position() + m.start(), c.KeepAnchor)
c.insertText(repl)
editor.setTextCursor(c)
if __name__ == '__main__':
from calibre.gui2.tweak_book.editor.widget import launch_editor
launch_editor('''\
<!DOCTYPE html>
<html xml:lang="en" lang="en">
<!--
-->
<head>
<meta charset="utf-8" />
<title>A title with a tag <span> in it, the tag is treated as normal text</title>
<style type="text/css">
body {
color: green;
font-size: 12pt;
}
</style>
<style type="text/css">p.small { font-size: x-small; color:gray }</style>
</head id="invalid attribute on closing tag">
<body lang="en_IN"><p:
<!-- The start of the actual body text -->
<h1 lang="en_US">A heading that should appear in bold, with an <i>italic</i> word</h1>
<p>Some text with inline formatting, that is syntax highlighted. A <b>bold</b> word, and an <em>italic</em> word. \
<i>Some italic text with a <b>bold-italic</b> word in </i>the middle.</p>
<!-- Let's see what exotic constructs like namespace prefixes and empty attributes look like -->
<svg:svg xmlns:svg="http://whatever" />
<input disabled><input disabled /><span attr=<></span>
<!-- Non-breaking spaces are rendered differently from normal spaces, so that they stand out -->
<p>Some\xa0words\xa0separated\xa0by\xa0non\u2011breaking\xa0spaces and non\u2011breaking hyphens.</p>
<p>Some non-BMP unicode text:\U0001f431\U0001f431\U0001f431</p>
</body>
</html>
''', path_is_raw=True, syntax='xml')

View File

@ -35,10 +35,10 @@ class Smarts(NullSmarts):
def handle_key_press(self, ev, editor):
key = ev.key()
if key == Qt.Key_Tab and smart_tab(editor, ev, tw):
if key == Qt.Key_Tab and smart_tab(editor, ev):
return True
elif key == Qt.Key_Backspace and smart_backspace(editor, ev, tw):
elif key == Qt.Key_Backspace and smart_backspace(editor, ev):
return True
elif key in (Qt.Key_Enter, Qt.Key_Return):

View File

@ -65,10 +65,11 @@ def smart_home(editor, ev):
def expand_tabs(text, tw):
return text.replace('\t', ' ' * tw)
def smart_tab(editor, ev, tw):
def smart_tab(editor, ev):
cursor, text = get_text_before_cursor(editor)
if not text.lstrip():
# cursor is preceded by only whitespace
tw = editor.tw
text = expand_tabs(text, tw)
spclen = len(text) - (len(text) % tw) + tw
cursor.insertText(' ' * spclen)
@ -76,10 +77,11 @@ def smart_tab(editor, ev, tw):
return True
return False
def smart_backspace(editor, ev, tw):
def smart_backspace(editor, ev):
cursor, text = get_text_before_cursor(editor)
if text and not text.lstrip():
# cursor is preceded by only whitespace
tw = editor.tw
text = expand_tabs(text, tw)
spclen = max(0, len(text) - (len(text) % tw) - tw)
cursor.insertText(' ' * spclen)

View File

@ -139,6 +139,7 @@ class TextEdit(PlainTextEdit):
def __init__(self, parent=None, expected_geometry=(100, 50)):
PlainTextEdit.__init__(self, parent)
self.gutter_width = 0
self.tw = 2
self.expected_geometry = expected_geometry
self.saved_matches = {}
self.syntax = None
@ -175,8 +176,8 @@ class TextEdit(PlainTextEdit):
self.apply_theme(theme)
w = self.fontMetrics()
self.space_width = w.width(' ')
tw = self.smarts.override_tab_stop_width if self.smarts.override_tab_stop_width is not None else prefs['editor_tab_stop_width']
self.setTabStopWidth(tw * self.space_width)
self.tw = self.smarts.override_tab_stop_width if self.smarts.override_tab_stop_width is not None else prefs['editor_tab_stop_width']
self.setTabStopWidth(self.tw * self.space_width)
if dictionaries_changed:
self.highlighter.rehighlight()
@ -223,7 +224,8 @@ class TextEdit(PlainTextEdit):
if sclass is not None:
self.smarts = sclass(self)
if self.smarts.override_tab_stop_width is not None:
self.setTabStopWidth(self.smarts.override_tab_stop_width * self.space_width)
self.tw = self.smarts.override_tab_stop_width
self.setTabStopWidth(self.tw * self.space_width)
self.setPlainText(unicodedata.normalize('NFC', unicode(text)))
if process_template and QPlainTextEdit.find(self, '%CURSOR%'):
c = self.textCursor()