From 95d8f9ba814f9723e2c0273202aaa2fc3e86b3c2 Mon Sep 17 00:00:00 2001 From: "44274505+ericricky@users.noreply.github.com" <44274505+ericricky@users.noreply.github.com> Date: Fri, 9 Nov 2018 14:33:50 -0600 Subject: Add word/character transposition at the console Inspired by emacs/readline. --- ranger/config/rc.conf | 2 ++ ranger/gui/widgets/console.py | 75 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) 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 eval fm.ui.console.delete_word(backward=False) cmap eval fm.ui.console.delete_rest(1) cmap eval fm.ui.console.delete_rest(-1) cmap eval fm.ui.console.paste() +cmap eval fm.ui.console.transpose_chars() +cmap eval fm.ui.console.transpose_words() # And of course the emacs way copycmap 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] -- cgit 1.4.1-2-gfad0