From 8a5a5bb644998fe03d8ab90d714c21d1552171a2 Mon Sep 17 00:00:00 2001
From: mathieui <mathieui@mathieui.net>
Date: Thu, 4 Apr 2013 01:03:18 +0200
Subject: Fix #2255 (search in input history)

- The input is split in two parts: on the left is what the user enters,
    on the right is the first match (the right part has a different
    color)
- Start and cancel a search with ^R
- Validate a search with enter, then press another time enter to send

- CommandInput and MessageInput now inherit from the HistoryInput class
  and share some methods
---
 src/tabs.py    |   1 +
 src/windows.py | 211 +++++++++++++++++++++++++++++++--------------------------
 2 files changed, 115 insertions(+), 97 deletions(-)

(limited to 'src')

diff --git a/src/tabs.py b/src/tabs.py
index 5d019bd1..7b7ee485 100644
--- a/src/tabs.py
+++ b/src/tabs.py
@@ -3444,6 +3444,7 @@ class MucListTab(Tab):
     def reset_help_message(self, _=None):
         curses.curs_set(0)
         self.input = self.default_help_message
+        self.input.resize(1, self.width, self.height-1, 0)
         return True
 
     def execute_slash_command(self, txt):
diff --git a/src/windows.py b/src/windows.py
index 7480a5dc..56eb3ab4 100644
--- a/src/windows.py
+++ b/src/windows.py
@@ -1456,7 +1456,7 @@ class Input(Win):
 
     def get_text(self):
         """
-        Clear the input and return the text entered so far
+        Return the text entered so far
         """
         return self.text
 
@@ -1495,24 +1495,53 @@ class Input(Win):
         self.clear_text()
         return txt
 
-class MessageInput(Input):
+class HistoryInput(Input):
     """
-    The input featuring history and that is being used in
-    Conversation, Muc and Private tabs
-    Also letting the user enter colors or other text markups
+    An input with colors and stuff, plus an history
+    ^R allows to search inside the history (as in a shell)
     """
-    history = list()            # The history is common to all MessageInput
-    text_attributes = set(('b', 'o', 'u'))
+    history = list()
 
     def __init__(self):
         Input.__init__(self)
-        self.last_completion = None
-        self.histo_pos = -1
-        self.key_func["KEY_UP"] = self.key_up
-        self.key_func["M-A"] =  self.key_up
-        self.key_func["KEY_DOWN"] = self.key_down
-        self.key_func["M-B"] = self.key_down
-        self.key_func['^C'] = self.enter_attrib
+        self.help_message = ''
+        self.current_completed = ''
+        self.key_func['^R'] = self.toggle_search
+        self.search = False
+
+    def toggle_search(self):
+        if self.help_message:
+            return
+        self.search = not self.search
+        self.refresh()
+
+    def update_completed(self):
+        """
+        Find a match for the current text
+        """
+        if not self.text:
+            return
+        for i in self.history:
+            if self.text in i:
+                self.current_completed = i
+                return
+        self.current_completed = ''
+
+    def history_enter(self):
+        """
+        Enter was pressed, set the text to the
+        current completion and disable history
+        search
+        """
+        if self.search:
+            self.search = False
+            if self.current_completed:
+                self.text = self.current_completed
+                self.current_completed = ''
+            self.refresh()
+            return True
+        self.refresh()
+        return False
 
     def key_up(self):
         """
@@ -1520,24 +1549,15 @@ class MessageInput(Input):
         """
         self.reset_completion()
         if self.histo_pos == -1 and self.get_text():
-            if not MessageInput.history or MessageInput.history[0] != self.get_text():
+            if not self.history or self.history[0] != self.get_text():
                 # add the message to history, we do not want to lose it
                 MessageInput.history.insert(0, self.get_text())
                 self.histo_pos += 1
-        if self.histo_pos < len(MessageInput.history) - 1:
+        if self.histo_pos < len(self.history) - 1:
             self.histo_pos += 1
-            self.text = MessageInput.history[self.histo_pos]
+            self.text = self.history[self.histo_pos]
         self.key_end()
 
-    def enter_attrib(self):
-        """
-        Read one more char (c) and add \x19c to the string
-        """
-        attr_char = self.core.read_keyboard()[0]
-        if attr_char in self.text_attributes or attr_char in allowed_color_digits:
-            self.do_command('\x19', False)
-            self.do_command(attr_char)
-
     def key_down(self):
         """
         Get the next line in the history
@@ -1545,48 +1565,90 @@ class MessageInput(Input):
         self.reset_completion()
         if self.histo_pos > 0:
             self.histo_pos -= 1
-            self.text = MessageInput.history[self.histo_pos]
+            self.text = self.history[self.histo_pos]
         elif self.histo_pos <= 0 and self.get_text():
-            if not MessageInput.history or MessageInput.history[0] != self.get_text():
+            if not self.history or self.history[0] != self.get_text():
                 # add the message to history, we do not want to lose it
-                MessageInput.history.insert(0, self.get_text())
+                self.history.insert(0, self.get_text())
             self.text = ''
             self.histo_pos = -1
         self.key_end()
 
-    def key_enter(self):
-        txt = self.get_text()
-        if len(txt) != 0:
-            if not MessageInput.history or MessageInput.history[0] != txt:
-                # add the message to history, but avoid duplicates
-                MessageInput.history.insert(0, txt)
-            self.histo_pos = -1
-        self.clear_text()
-        return txt
-
     def rewrite_text(self):
         """
-        Refresh the line onscreen, from the pos and pos_line, with colors
+        Rewrite the text just like a normal input, but with the instruction
+        on the left or a "completion bar" on the right (those are mutually
+        exclusive)
         """
         with g_lock:
-            # Replace \t with ' ' just to make the input easily editable.
-            # That's not perfect, because we cannot differenciate a tab and
-            # a space. But at least it makes it possible to paste text
-            # containing a tab by sending a real tab, not just four spaces
-            # while still being able to edit the input in that case.
             text = self.text.replace('\n', '|').replace('\t', ' ')
             self._win.erase()
+            if self.help_message:
+                self.addstr(self.help_message, to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
+                text_pos = len(self.help_message) + 1
+                self.addstr(' ')
+            else:
+                text_pos = 0
             if self.color:
                 self._win.attron(to_curses_attr(self.color))
-            displayed_text = text[self.line_pos:self.line_pos+self.width-1]
+
+            width = self.width // 2 if self.search else self.width
+            displayed_text = text[self.line_pos:self.line_pos+width-1]
+
             self._win.attrset(0)
             self.addstr_colored_lite(displayed_text)
-            self.addstr(0, wcwidth.wcswidth(displayed_text[:self.pos]), '')
+
+            if self.search:
+                self.update_completed()
+                self.addstr(0, width, self.current_completed.ljust(width+1, ' '), to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
+
+            self.addstr(0, wcwidth.wcswidth(displayed_text[:self.pos]) + text_pos, '')
             if self.color:
                 self._win.attroff(to_curses_attr(self.color))
             self._refresh()
 
-class CommandInput(Input):
+class MessageInput(HistoryInput):
+    """
+    The input featuring history and that is being used in
+    Conversation, Muc and Private tabs
+    Also letting the user enter colors or other text markups
+    """
+    history = list()            # The history is common to all MessageInput
+    text_attributes = set(('b', 'o', 'u'))
+
+    def __init__(self):
+        HistoryInput.__init__(self)
+        self.last_completion = None
+        self.histo_pos = -1
+        self.key_func["KEY_UP"] = self.key_up
+        self.key_func["M-A"] =  self.key_up
+        self.key_func["KEY_DOWN"] = self.key_down
+        self.key_func["M-B"] = self.key_down
+        self.key_func['^C'] = self.enter_attrib
+
+    def enter_attrib(self):
+        """
+        Read one more char (c) and add \x19c to the string
+        """
+        attr_char = self.core.read_keyboard()[0]
+        if attr_char in self.text_attributes or attr_char in allowed_color_digits:
+            self.do_command('\x19', False)
+            self.do_command(attr_char)
+
+    def key_enter(self):
+        if self.history_enter():
+            return
+
+        txt = self.get_text()
+        if len(txt) != 0:
+            if not self.history or self.history[0] != txt:
+                # add the message to history, but avoid duplicates
+                self.history.insert(0, txt)
+            self.histo_pos = -1
+        self.clear_text()
+        return txt
+
+class CommandInput(HistoryInput):
     """
     An input with an help message in the left, with three given callbacks:
     one when when successfully 'execute' the command and when we abort it.
@@ -1598,7 +1660,7 @@ class CommandInput(Input):
     history = list()
 
     def __init__(self, help_message, on_abort, on_success, on_input=None):
-        Input.__init__(self)
+        HistoryInput.__init__(self)
         self.on_abort = on_abort
         self.on_success = on_success
         self.on_input = on_input
@@ -1636,8 +1698,10 @@ class CommandInput(Input):
         call the success callback, passing the text as argument
         """
         self.on_input = None
+        if self.search:
+            self.history_enter()
         res = self.on_success(self.get_text())
-        return  res
+        return res
 
     def abort(self):
         """
@@ -1646,22 +1710,6 @@ class CommandInput(Input):
         self.on_input = None
         return self.on_abort(self.get_text())
 
-    def rewrite_text(self):
-        """
-        Rewrite the text just like a normal input, but with the instruction
-        on the left
-        """
-        with g_lock:
-            self._win.erase()
-            self.addstr(self.help_message, to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
-            cursor_pos = self.pos + len(self.help_message)
-            if len(self.help_message):
-                self.addstr(' ')
-                cursor_pos += 1
-            self.addstr(self.text[self.line_pos:self.line_pos+self.width-1])
-            self.addstr(0, cursor_pos, '') # WTF, this works but .move() doesn't…
-            self._refresh()
-
     def on_delete(self):
         """
         SERIOUSLY BIG WTF.
@@ -1678,37 +1726,6 @@ class CommandInput(Input):
         self.on_input = None
         self.key_func.clear()
 
-    def key_up(self):
-        """
-        Get the previous line in the history
-        """
-        self.reset_completion()
-        if self.histo_pos == -1 and self.get_text():
-            if not self.history or self.history[0] != self.get_text():
-                # add the message to history, we do not want to lose it
-                self.history.insert(0, self.get_text())
-                self.histo_pos += 1
-        if self.histo_pos < len(self.history) - 1:
-            self.histo_pos += 1
-            self.text = self.history[self.histo_pos]
-        self.key_end()
-
-    def key_down(self):
-        """
-        Get the next line in the history
-        """
-        self.reset_completion()
-        if self.histo_pos > 0:
-            self.histo_pos -= 1
-            self.text = self.history[self.histo_pos]
-        elif self.histo_pos <= 0 and self.get_text():
-            if not self.history or self.history[0] != self.get_text():
-                # add the message to history, we do not want to lose it
-                self.history.insert(0, self.get_text())
-            self.text = ''
-            self.histo_pos = -1
-        self.key_end()
-
     def key_enter(self):
         txt = self.get_text()
         if len(txt) != 0:
-- 
cgit v1.2.3