about summary refs log tree commit diff stats
diff options
context:
space:
mode:
author44274505+ericricky@users.noreply.github.com <44274505+ericricky@users.noreply.github.com>2018-11-09 14:33:50 -0600
committertoonn <toonn@toonn.io>2021-01-16 21:30:14 +0100
commit95d8f9ba814f9723e2c0273202aaa2fc3e86b3c2 (patch)
treec4dbe3deb50ca53b83cffa4f60b08bcba1ed2ea9
parent9ac5cceab5863f36b4f8cdd9ff4f0769072237d8 (diff)
downloadranger-95d8f9ba814f9723e2c0273202aaa2fc3e86b3c2.tar.gz
Add word/character transposition at the console
Inspired by emacs/readline.
-rw-r--r--ranger/config/rc.conf2
-rw-r--r--ranger/gui/widgets/console.py75
2 files changed, 77 insertions, 0 deletions
diff --git a/ranger/config/rc.conf b/ranger/config/rc.conf
index d7d79f97..dc6c7717 100644
--- a/ranger/config/rc.conf
+++ b/ranger/config/rc.conf
@@ -671,6 +671,8 @@ cmap <A-d>        eval fm.ui.console.delete_word(backward=False)
 cmap <C-k>        eval fm.ui.console.delete_rest(1)
 cmap <C-u>        eval fm.ui.console.delete_rest(-1)
 cmap <C-y>        eval fm.ui.console.paste()
+cmap <C-t>        eval fm.ui.console.transpose_chars()
+cmap <A-t>        eval fm.ui.console.transpose_words()
 
 # And of course the emacs way
 copycmap <ESC>       <C-g>
diff --git a/ranger/gui/widgets/console.py b/ranger/gui/widgets/console.py
index 88a59136..a6d9117c 100644
--- a/ranger/gui/widgets/console.py
+++ b/ranger/gui/widgets/console.py
@@ -439,6 +439,81 @@ class Console(Widget):  # pylint: disable=too-many-instance-attributes,too-many-
             self.line = left_part + ''.join(uchar[upos + 1:]).encode('utf-8', 'ignore')
         self.on_line_change()
 
+    def transpose_subr(self, line, x, y):
+        # Transpose substrings x & y of line
+        # x & y are tuples of length two containing positions of endpoints
+        if not (x[0] <= x[1] <= y[0] <= y[1] <= len(line)
+                or x[0] == y[0] <= x[1] == y[1] <= len(line)):
+            self.fm.notify("Tried to transpose invalid regions.", bad=True)
+            return line
+
+        word_x = line[x[0]:x[1]]
+        word_y = line[y[0]:y[1]]
+        diff = len(word_y) - len(word_x)
+
+        line_begin = line[0:x[0]]
+        line_end = line[x[1]:]
+
+        line = line_begin + word_y + line_end
+
+        line_begin = line[0:(y[0] + diff)]
+        line_end = line[(y[1] + diff):]
+
+        line = line_begin + word_x + line_end
+        return line
+
+    def transpose_chars(self):
+        if self.pos == len(self.line):
+            x = max(0, self.pos - 2), max(0, self.pos - 1)
+            y = max(0, self.pos - 1), self.pos
+        else:
+            x = max(0, self.pos - 1), self.pos
+            y = self.pos, min(len(self.line), self.pos + 1)
+        self.line = self.transpose_subr(self.line, x, y)
+        self.pos = y[1]
+        self.on_line_change()
+
+    def transpose_words(self):
+        # Interchange adjacent words at the console with Alt-t
+        # like in Emacs and many terminal emulators
+        if self.line:
+            # If before the first word, interchange next two words
+            if not re.search(r'[\w\d]', self.line[:self.pos], re.UNICODE):
+                self.pos = self.move_by_word(self.line, self.pos, 1)
+
+            # If in/after last word, interchange last two words
+            if re.match(r'[\w\d]*\s*$', self.line[self.pos:], re.UNICODE):
+                self.pos = self.move_by_word(self.line, self.pos, -1)
+
+            # Util function to increment position until out of word/whitespace
+            def _traverse(line, pos, regex):
+                while pos < len(line) and re.match(
+                        regex, line[pos], re.UNICODE):
+                    pos += 1
+                return pos
+
+            # Calculate endpoints of target words and pass them to
+            # 'self.transpose_subr'
+            x_begin = self.move_by_word(self.line, self.pos, -1)
+            x_end = _traverse(self.line, x_begin, r'[\w\d]')
+            x = x_begin, x_end
+
+            y_begin = self.pos
+
+            # If in middle of word, move to end
+            if re.match(r'[\w\d]', self.line[self.pos - 1], re.UNICODE):
+                y_begin = _traverse(self.line, y_begin, r'[\w\d]')
+
+            # Traverse whitespace to beginning of next word
+            y_begin = _traverse(self.line, y_begin, r'\s')
+
+            y_end = _traverse(self.line, y_begin, r'[\w\d]')
+            y = y_begin, y_end
+
+            self.line = self.transpose_subr(self.line, x, y)
+            self.pos = y[1]
+            self.on_line_change()
+
     def execute(self, cmd=None):
         if self.question_queue and cmd is None:
             question = self.question_queue[0]