diff --git a/src/calibre/gui2/tweak_book/editor/smarts/css.py b/src/calibre/gui2/tweak_book/editor/smarts/css.py index 5f784d9aa5..b153878fbc 100644 --- a/src/calibre/gui2/tweak_book/editor/smarts/css.py +++ b/src/calibre/gui2/tweak_book/editor/smarts/css.py @@ -72,7 +72,7 @@ class Smarts(NullSmarts): if key == Qt.Key.Key_Home and smart_home(editor, ev): return True - if key == Qt.Key.Key_Tab: + if key in (Qt.Key.Key_Tab, Qt.Key.Key_Backtab): mods = ev.modifiers() if not mods & Qt.KeyboardModifier.ControlModifier and smart_tab(editor, ev): return True diff --git a/src/calibre/gui2/tweak_book/editor/smarts/html.py b/src/calibre/gui2/tweak_book/editor/smarts/html.py index c6b2e6e754..139610fa15 100644 --- a/src/calibre/gui2/tweak_book/editor/smarts/html.py +++ b/src/calibre/gui2/tweak_book/editor/smarts/html.py @@ -732,7 +732,7 @@ class Smarts(NullSmarts): if key == Qt.Key.Key_Home and smart_home(editor, ev): return True - if key == Qt.Key.Key_Tab: + if key in (Qt.Key.Key_Tab, Qt.Key.Key_Backtab): if not mods & Qt.KeyboardModifier.ControlModifier and smart_tab(editor, ev): return True diff --git a/src/calibre/gui2/tweak_book/editor/smarts/python.py b/src/calibre/gui2/tweak_book/editor/smarts/python.py index b768a4a669..4e8a0a090a 100644 --- a/src/calibre/gui2/tweak_book/editor/smarts/python.py +++ b/src/calibre/gui2/tweak_book/editor/smarts/python.py @@ -36,7 +36,7 @@ class Smarts(NullSmarts): def handle_key_press(self, ev, editor): key = ev.key() - if key == Qt.Key.Key_Tab: + if key in (Qt.Key.Key_Tab, Qt.Key.Key_Backtab): mods = ev.modifiers() if not mods & Qt.KeyboardModifier.ControlModifier and smart_tab(editor, ev): return True diff --git a/src/calibre/gui2/tweak_book/editor/smarts/utils.py b/src/calibre/gui2/tweak_book/editor/smarts/utils.py index ff1f2a65f8..8ece27b29e 100644 --- a/src/calibre/gui2/tweak_book/editor/smarts/utils.py +++ b/src/calibre/gui2/tweak_book/editor/smarts/utils.py @@ -74,19 +74,73 @@ def expand_tabs(text, tw): return text.replace('\t', ' ' * tw) -def smart_tab(editor, ev): +def smart_tab_if_whitespace_only_before_cursor(editor, backwards): 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) - editor.setTextCursor(cursor) - return True + if backwards: + if leading := len(text): + new_leading = max(0, leading - tw) + extra = new_leading % tw + if extra: + new_leading += tw - extra + cursor.insertText(' ' * new_leading) + return True + else: + spclen = len(text) - (len(text) % tw) + tw + cursor.insertText(' ' * spclen) + editor.setTextCursor(cursor) + return True return False +def smart_tab_all_blocks_in_selection(editor, backwards): + cursor = editor.textCursor() + c = QTextCursor(cursor) + c.clearSelection() + c.setPosition(cursor.selectionStart()) + tab_width = editor.tw + changed = False + while not c.atEnd() and c.position() <= cursor.selectionEnd(): + c.clearSelection() + c.movePosition(QTextCursor.MoveOperation.EndOfBlock) + c.movePosition(QTextCursor.MoveOperation.StartOfBlock, QTextCursor.MoveMode.KeepAnchor) + c.movePosition(QTextCursor.MoveOperation.StartOfBlock) + # select leading whitespace + while not c.atEnd() and c.document().characterAt(c.position()).isspace(): + c.movePosition(QTextCursor.MoveOperation.NextCharacter, QTextCursor.MoveMode.KeepAnchor) + text = expand_tabs(editor.selected_text_from_cursor(c), tab_width) + leading = len(text) + replaced = False + if backwards: + if leading: + new_leading = max(0, leading - tab_width) + extra = new_leading % tab_width + if extra: + new_leading += tab_width - extra + replaced = True + else: + new_leading = leading + tab_width + new_leading -= new_leading % tab_width + replaced = True + if replaced: + c.insertText(' ' * new_leading) + changed = True + c.movePosition(QTextCursor.MoveOperation.NextBlock) + c.movePosition(QTextCursor.MoveOperation.StartOfBlock) + return changed + + +def smart_tab(editor, ev): + cursor = editor.textCursor() + backwards = ev.key() == Qt.Key.Key_Backtab or (ev.key() == Qt.Key.Key_Tab and bool(ev.modifiers() & Qt.KeyboardModifier.ShiftModifier)) + if cursor.hasSelection(): + return smart_tab_all_blocks_in_selection(editor, backwards) + return smart_tab_if_whitespace_only_before_cursor(editor, backwards) + + def smart_backspace(editor, ev): if editor.textCursor().hasSelection(): return False