diff --git a/porcupine/plugins/sort.py b/porcupine/plugins/sort.py index 06f4aa97f..b25a65ab1 100644 --- a/porcupine/plugins/sort.py +++ b/porcupine/plugins/sort.py @@ -1,26 +1,46 @@ """ -Sort the selected lines alphabetically, or if there's no selection, sort between surrounding blank lines. +Sort the selected lines alphabetically. Available in menubar → Edit → Sort Lines. -Available in Edit/Sort Lines. +To sort lines that are indented or surrounded by blank lines, you can simply +place the cursor somewhere in the middle without selecting anything and run the +sort. In other words, if there is nothing selected, this plugin will select +lines above and below the cursor location, excluding blank lines or any lines +with less indentation. """ +from __future__ import annotations import tkinter - from porcupine import menubar, tabs, textutils +def can_extend_default_selection(text: tkinter.Text, required_prefix: str, new_lineno: int) -> bool: + line = text.get(f"{new_lineno}.0", f"{new_lineno + 1}.0") + return bool(line.strip()) and line.startswith(required_prefix) + + +def find_chunk_around_cursor(text: tkinter.Text) -> tuple[int, int]: + cursor_lineno = int(text.index("insert").split(".")[0]) + cursor_line = text.get("insert linestart", "insert lineend") + cursor_line_indentation = cursor_line[:(len(cursor_line) - len(cursor_line.lstrip()))] + + start = cursor_lineno + while can_extend_default_selection(text, cursor_line_indentation, start - 1): + start -= 1 + + end = cursor_lineno + while can_extend_default_selection(text, cursor_line_indentation, end + 1): + end += 1 + + return (start, end) + + def sort(tab: tabs.FileTab) -> None: try: first_line = int(tab.textwidget.index("sel.first").split(".")[0]) # If last selected character is newline, ignore it last_line = int(tab.textwidget.index("sel.last - 1 char").split(".")[0]) except tkinter.TclError: - # Nothing selected, find blank line separated part - first_line = last_line = int(tab.textwidget.index("insert").split(".")[0]) - while tab.textwidget.get(f"{first_line - 1}.0", f"{first_line}.0").strip(): - first_line -= 1 - while tab.textwidget.get(f"{last_line + 1}.0", f"{last_line + 2}.0").strip(): - last_line += 1 + first_line, last_line = find_chunk_around_cursor(tab.textwidget) old_lines = tab.textwidget.get(f"{first_line}.0", f"{last_line}.0 lineend").splitlines() new_lines = sorted(old_lines) diff --git a/tests/test_sort_plugin.py b/tests/test_sort_plugin.py index e129f75a5..ac15521f2 100644 --- a/tests/test_sort_plugin.py +++ b/tests/test_sort_plugin.py @@ -52,6 +52,37 @@ def test_finding_blank_line_separated_block(filetab): ) +def test_finding_indented_block(filetab): + filetab.textwidget.insert( + "end", + """\ +[foo] + dingdingding + akuli + catris + banana + +blah blah +""") + + filetab.textwidget.mark_set("insert", "3.3") + filetab.event_generate("<>") + assert filetab.textwidget.index("insert") == "3.3" # not moved + assert ( + filetab.textwidget.get("1.0", "end - 1 char") + == """\ +[foo] + akuli + banana + catris + dingdingding + +blah blah +""" + ) + assert filetab.textwidget.get("sel.first", "sel.last") == " akuli\n banana\n catris\n dingdingding\n" + + def test_just_sorting_the_whole_file(filetab): filetab.textwidget.insert("end", "bbb\nccc\naaa") filetab.textwidget.mark_set("insert", "2.0")