We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Add small delay before updating completion
A small delay before updating the completion might help to reduce load when an user is e.g. holding backspace.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `qutebrowser/completion/completer.py`
Content:
```
1 # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
2 
3 # Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
4 #
5 # This file is part of qutebrowser.
6 #
7 # qutebrowser is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # qutebrowser is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with qutebrowser.  If not, see <http://www.gnu.org/licenses/>.
19 
20 """Completer attached to a CompletionView."""
21 
22 import attr
23 from PyQt5.QtCore import pyqtSlot, QObject, QTimer
24 
25 from qutebrowser.config import config
26 from qutebrowser.commands import cmdutils, runners
27 from qutebrowser.utils import log, utils, debug
28 from qutebrowser.completion.models import miscmodels
29 
30 
31 @attr.s
32 class CompletionInfo:
33 
34     """Context passed into all completion functions."""
35 
36     config = attr.ib()
37     keyconf = attr.ib()
38 
39 
40 class Completer(QObject):
41 
42     """Completer which manages completions in a CompletionView.
43 
44     Attributes:
45         _cmd: The statusbar Command object this completer belongs to.
46         _ignore_change: Whether to ignore the next completion update.
47         _timer: The timer used to trigger the completion update.
48         _last_cursor_pos: The old cursor position so we avoid double completion
49                           updates.
50         _last_text: The old command text so we avoid double completion updates.
51         _last_completion_func: The completion function used for the last text.
52     """
53 
54     def __init__(self, cmd, parent=None):
55         super().__init__(parent)
56         self._cmd = cmd
57         self._ignore_change = False
58         self._timer = QTimer()
59         self._timer.setSingleShot(True)
60         self._timer.setInterval(0)
61         self._timer.timeout.connect(self._update_completion)
62         self._last_cursor_pos = None
63         self._last_text = None
64         self._last_completion_func = None
65         self._cmd.update_completion.connect(self.schedule_completion_update)
66 
67     def __repr__(self):
68         return utils.get_repr(self)
69 
70     def _model(self):
71         """Convenience method to get the current completion model."""
72         completion = self.parent()
73         return completion.model()
74 
75     def _get_new_completion(self, before_cursor, under_cursor):
76         """Get the completion function based on the current command text.
77 
78         Args:
79             before_cursor: The command chunks before the cursor.
80             under_cursor: The command chunk under the cursor.
81 
82         Return:
83             A completion model.
84         """
85         if '--' in before_cursor or under_cursor.startswith('-'):
86             # cursor on a flag or after an explicit split (--)
87             return None
88         log.completion.debug("Before removing flags: {}".format(before_cursor))
89         before_cursor = [x for x in before_cursor if not x.startswith('-')]
90         log.completion.debug("After removing flags: {}".format(before_cursor))
91         if not before_cursor:
92             # '|' or 'set|'
93             log.completion.debug('Starting command completion')
94             return miscmodels.command
95         try:
96             cmd = cmdutils.cmd_dict[before_cursor[0]]
97         except KeyError:
98             log.completion.debug("No completion for unknown command: {}"
99                                  .format(before_cursor[0]))
100             return None
101         argpos = len(before_cursor) - 1
102         try:
103             func = cmd.get_pos_arg_info(argpos).completion
104         except IndexError:
105             log.completion.debug("No completion in position {}".format(argpos))
106             return None
107         return func
108 
109     def _quote(self, s):
110         """Quote s if it needs quoting for the commandline.
111 
112         Note we don't use shlex.quote because that quotes a lot of shell
113         metachars we don't need to have quoted.
114         """
115         if not s:
116             return "''"
117         elif any(c in s for c in ' "\'\t\n\\'):
118             # use single quotes, and put single quotes into double quotes
119             # the string $'b is then quoted as '$'"'"'b'
120             return "'" + s.replace("'", "'\"'\"'") + "'"
121         else:
122             return s
123 
124     def _partition(self):
125         """Divide the commandline text into chunks around the cursor position.
126 
127         Return:
128             ([parts_before_cursor], 'part_under_cursor', [parts_after_cursor])
129         """
130         text = self._cmd.text()[len(self._cmd.prefix()):]
131         if not text or not text.strip():
132             # Only ":", empty part under the cursor with nothing before/after
133             return [], '', []
134         parser = runners.CommandParser()
135         result = parser.parse(text, fallback=True, keep=True)
136         # pylint: disable=not-an-iterable
137         parts = [x for x in result.cmdline if x]
138         # pylint: enable=not-an-iterable
139         pos = self._cmd.cursorPosition() - len(self._cmd.prefix())
140         pos = min(pos, len(text))  # Qt treats 2-byte UTF-16 chars as 2 chars
141         log.completion.debug('partitioning {} around position {}'.format(parts,
142                                                                          pos))
143         for i, part in enumerate(parts):
144             pos -= len(part)
145             if pos <= 0:
146                 if part[pos-1:pos+1].isspace():
147                     # cursor is in a space between two existing words
148                     parts.insert(i, '')
149                 prefix = [x.strip() for x in parts[:i]]
150                 center = parts[i].strip()
151                 # strip trailing whitepsace included as a separate token
152                 postfix = [x.strip() for x in parts[i+1:] if not x.isspace()]
153                 log.completion.debug(
154                     "partitioned: {} '{}' {}".format(prefix, center, postfix))
155                 return prefix, center, postfix
156 
157         # We should always return above
158         assert False, parts
159 
160     @pyqtSlot(str)
161     def on_selection_changed(self, text):
162         """Change the completed part if a new item was selected.
163 
164         Called from the views selectionChanged method.
165 
166         Args:
167             text: Newly selected text.
168         """
169         if text is None:
170             return
171         before, center, after = self._partition()
172         log.completion.debug("Changing {} to '{}'".format(center, text))
173         try:
174             maxsplit = cmdutils.cmd_dict[before[0]].maxsplit
175         except (KeyError, IndexError):
176             maxsplit = None
177         if maxsplit is None:
178             text = self._quote(text)
179         model = self._model()
180         if model.count() == 1 and config.val.completion.quick:
181             # If we only have one item, we want to apply it immediately
182             # and go on to the next part.
183             self._change_completed_part(text, before, after, immediate=True)
184             if maxsplit is not None and maxsplit < len(before):
185                 # If we are quick-completing the part after maxsplit, don't
186                 # keep offering completions (see issue #1519)
187                 self._ignore_change = True
188         else:
189             log.completion.debug("Will ignore next completion update.")
190             self._ignore_change = True
191             self._change_completed_part(text, before, after)
192 
193     @pyqtSlot()
194     def schedule_completion_update(self):
195         """Schedule updating/enabling completion.
196 
197         For performance reasons we don't want to block here, instead we do this
198         in the background.
199         """
200         if (self._cmd.cursorPosition() == self._last_cursor_pos and
201                 self._cmd.text() == self._last_text):
202             log.completion.debug("Ignoring update because there were no "
203                                  "changes.")
204         else:
205             log.completion.debug("Scheduling completion update.")
206             self._timer.start()
207         self._last_cursor_pos = self._cmd.cursorPosition()
208         self._last_text = self._cmd.text()
209 
210     @pyqtSlot()
211     def _update_completion(self):
212         """Check if completions are available and activate them."""
213         if self._ignore_change:
214             log.completion.debug("Ignoring completion update because "
215                                  "ignore_change is True.")
216             self._ignore_change = False
217             return
218 
219         completion = self.parent()
220 
221         if self._cmd.prefix() != ':':
222             # This is a search or gibberish, so we don't need to complete
223             # anything (yet)
224             # FIXME complete searches
225             # https://github.com/qutebrowser/qutebrowser/issues/32
226             completion.set_model(None)
227             self._last_completion_func = None
228             return
229 
230         before_cursor, pattern, after_cursor = self._partition()
231 
232         log.completion.debug("Updating completion: {} {} {}".format(
233             before_cursor, pattern, after_cursor))
234 
235         pattern = pattern.strip("'\"")
236         func = self._get_new_completion(before_cursor, pattern)
237 
238         if func is None:
239             log.completion.debug('Clearing completion')
240             completion.set_model(None)
241             self._last_completion_func = None
242             return
243 
244         if func != self._last_completion_func:
245             self._last_completion_func = func
246             args = (x for x in before_cursor[1:] if not x.startswith('-'))
247             with debug.log_time(log.completion,
248                     'Starting {} completion'.format(func.__name__)):
249                 info = CompletionInfo(config=config.instance,
250                                       keyconf=config.key_instance)
251                 model = func(*args, info=info)
252             with debug.log_time(log.completion, 'Set completion model'):
253                 completion.set_model(model)
254 
255         completion.set_pattern(pattern)
256 
257     def _change_completed_part(self, newtext, before, after, immediate=False):
258         """Change the part we're currently completing in the commandline.
259 
260         Args:
261             text: The text to set (string) for the token under the cursor.
262             before: Commandline tokens before the token under the cursor.
263             after: Commandline tokens after the token under the cursor.
264             immediate: True if the text should be completed immediately
265                        including a trailing space and we shouldn't continue
266                        completing the current item.
267         """
268         text = self._cmd.prefix() + ' '.join(before + [newtext])
269         pos = len(text) + (1 if immediate else 0)
270         if after:
271             text += ' ' + ' '.join(after)
272         elif immediate:
273             # pad with a space if quick-completing the last entry
274             text += ' '
275         log.completion.debug("setting text = '{}', pos = {}".format(text, pos))
276         self._cmd.setText(text)
277         self._cmd.setCursorPosition(pos)
278         self._cmd.setFocus()
279         self._cmd.show_cmd.emit()
280
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/qutebrowser/completion/completer.py b/qutebrowser/completion/completer.py
--- a/qutebrowser/completion/completer.py
+++ b/qutebrowser/completion/completer.py
@@ -196,14 +196,25 @@
 
         For performance reasons we don't want to block here, instead we do this
         in the background.
+
+        We delay the update only if we've already input some text and ignore
+        updates if the text is shorter than completion.min_chars (unless we're
+        hitting backspace in which case updates won't be ignored).
         """
-        if (self._cmd.cursorPosition() == self._last_cursor_pos and
+        _cmd, _sep, rest = self._cmd.text().partition(' ')
+        input_length = len(rest)
+        if (0 < input_length < config.val.completion.min_chars and
+                self._cmd.cursorPosition() > self._last_cursor_pos):
+            log.completion.debug("Ignoring update because the length of "
+                                 "the text is less than completion.min_chars.")
+        elif (self._cmd.cursorPosition() == self._last_cursor_pos and
                 self._cmd.text() == self._last_text):
             log.completion.debug("Ignoring update because there were no "
                                  "changes.")
         else:
             log.completion.debug("Scheduling completion update.")
-            self._timer.start()
+            start_delay = config.val.completion.delay if self._last_text else 0
+            self._timer.start(start_delay)
         self._last_cursor_pos = self._cmd.cursorPosition()
         self._last_text = self._cmd.text()

{"golden_diff": "diff --git a/qutebrowser/completion/completer.py b/qutebrowser/completion/completer.py\n--- a/qutebrowser/completion/completer.py\n+++ b/qutebrowser/completion/completer.py\n@@ -196,14 +196,25 @@\n \n         For performance reasons we don't want to block here, instead we do this\n         in the background.\n+\n+        We delay the update only if we've already input some text and ignore\n+        updates if the text is shorter than completion.min_chars (unless we're\n+        hitting backspace in which case updates won't be ignored).\n         \"\"\"\n-        if (self._cmd.cursorPosition() == self._last_cursor_pos and\n+        _cmd, _sep, rest = self._cmd.text().partition(' ')\n+        input_length = len(rest)\n+        if (0 < input_length < config.val.completion.min_chars and\n+                self._cmd.cursorPosition() > self._last_cursor_pos):\n+            log.completion.debug(\"Ignoring update because the length of \"\n+                                 \"the text is less than completion.min_chars.\")\n+        elif (self._cmd.cursorPosition() == self._last_cursor_pos and\n                 self._cmd.text() == self._last_text):\n             log.completion.debug(\"Ignoring update because there were no \"\n                                  \"changes.\")\n         else:\n             log.completion.debug(\"Scheduling completion update.\")\n-            self._timer.start()\n+            start_delay = config.val.completion.delay if self._last_text else 0\n+            self._timer.start(start_delay)\n         self._last_cursor_pos = self._cmd.cursorPosition()\n         self._last_text = self._cmd.text()\n", "issue": "Add small delay before updating completion\nA small delay before updating the completion might help to reduce load when an user is e.g. holding backspace.\n\n", "before_files": [{"content": "# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:\n\n# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>\n#\n# This file is part of qutebrowser.\n#\n# qutebrowser is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# qutebrowser is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with qutebrowser.  If not, see <http://www.gnu.org/licenses/>.\n\n\"\"\"Completer attached to a CompletionView.\"\"\"\n\nimport attr\nfrom PyQt5.QtCore import pyqtSlot, QObject, QTimer\n\nfrom qutebrowser.config import config\nfrom qutebrowser.commands import cmdutils, runners\nfrom qutebrowser.utils import log, utils, debug\nfrom qutebrowser.completion.models import miscmodels\n\n\n@attr.s\nclass CompletionInfo:\n\n    \"\"\"Context passed into all completion functions.\"\"\"\n\n    config = attr.ib()\n    keyconf = attr.ib()\n\n\nclass Completer(QObject):\n\n    \"\"\"Completer which manages completions in a CompletionView.\n\n    Attributes:\n        _cmd: The statusbar Command object this completer belongs to.\n        _ignore_change: Whether to ignore the next completion update.\n        _timer: The timer used to trigger the completion update.\n        _last_cursor_pos: The old cursor position so we avoid double completion\n                          updates.\n        _last_text: The old command text so we avoid double completion updates.\n        _last_completion_func: The completion function used for the last text.\n    \"\"\"\n\n    def __init__(self, cmd, parent=None):\n        super().__init__(parent)\n        self._cmd = cmd\n        self._ignore_change = False\n        self._timer = QTimer()\n        self._timer.setSingleShot(True)\n        self._timer.setInterval(0)\n        self._timer.timeout.connect(self._update_completion)\n        self._last_cursor_pos = None\n        self._last_text = None\n        self._last_completion_func = None\n        self._cmd.update_completion.connect(self.schedule_completion_update)\n\n    def __repr__(self):\n        return utils.get_repr(self)\n\n    def _model(self):\n        \"\"\"Convenience method to get the current completion model.\"\"\"\n        completion = self.parent()\n        return completion.model()\n\n    def _get_new_completion(self, before_cursor, under_cursor):\n        \"\"\"Get the completion function based on the current command text.\n\n        Args:\n            before_cursor: The command chunks before the cursor.\n            under_cursor: The command chunk under the cursor.\n\n        Return:\n            A completion model.\n        \"\"\"\n        if '--' in before_cursor or under_cursor.startswith('-'):\n            # cursor on a flag or after an explicit split (--)\n            return None\n        log.completion.debug(\"Before removing flags: {}\".format(before_cursor))\n        before_cursor = [x for x in before_cursor if not x.startswith('-')]\n        log.completion.debug(\"After removing flags: {}\".format(before_cursor))\n        if not before_cursor:\n            # '|' or 'set|'\n            log.completion.debug('Starting command completion')\n            return miscmodels.command\n        try:\n            cmd = cmdutils.cmd_dict[before_cursor[0]]\n        except KeyError:\n            log.completion.debug(\"No completion for unknown command: {}\"\n                                 .format(before_cursor[0]))\n            return None\n        argpos = len(before_cursor) - 1\n        try:\n            func = cmd.get_pos_arg_info(argpos).completion\n        except IndexError:\n            log.completion.debug(\"No completion in position {}\".format(argpos))\n            return None\n        return func\n\n    def _quote(self, s):\n        \"\"\"Quote s if it needs quoting for the commandline.\n\n        Note we don't use shlex.quote because that quotes a lot of shell\n        metachars we don't need to have quoted.\n        \"\"\"\n        if not s:\n            return \"''\"\n        elif any(c in s for c in ' \"\\'\\t\\n\\\\'):\n            # use single quotes, and put single quotes into double quotes\n            # the string $'b is then quoted as '$'\"'\"'b'\n            return \"'\" + s.replace(\"'\", \"'\\\"'\\\"'\") + \"'\"\n        else:\n            return s\n\n    def _partition(self):\n        \"\"\"Divide the commandline text into chunks around the cursor position.\n\n        Return:\n            ([parts_before_cursor], 'part_under_cursor', [parts_after_cursor])\n        \"\"\"\n        text = self._cmd.text()[len(self._cmd.prefix()):]\n        if not text or not text.strip():\n            # Only \":\", empty part under the cursor with nothing before/after\n            return [], '', []\n        parser = runners.CommandParser()\n        result = parser.parse(text, fallback=True, keep=True)\n        # pylint: disable=not-an-iterable\n        parts = [x for x in result.cmdline if x]\n        # pylint: enable=not-an-iterable\n        pos = self._cmd.cursorPosition() - len(self._cmd.prefix())\n        pos = min(pos, len(text))  # Qt treats 2-byte UTF-16 chars as 2 chars\n        log.completion.debug('partitioning {} around position {}'.format(parts,\n                                                                         pos))\n        for i, part in enumerate(parts):\n            pos -= len(part)\n            if pos <= 0:\n                if part[pos-1:pos+1].isspace():\n                    # cursor is in a space between two existing words\n                    parts.insert(i, '')\n                prefix = [x.strip() for x in parts[:i]]\n                center = parts[i].strip()\n                # strip trailing whitepsace included as a separate token\n                postfix = [x.strip() for x in parts[i+1:] if not x.isspace()]\n                log.completion.debug(\n                    \"partitioned: {} '{}' {}\".format(prefix, center, postfix))\n                return prefix, center, postfix\n\n        # We should always return above\n        assert False, parts\n\n    @pyqtSlot(str)\n    def on_selection_changed(self, text):\n        \"\"\"Change the completed part if a new item was selected.\n\n        Called from the views selectionChanged method.\n\n        Args:\n            text: Newly selected text.\n        \"\"\"\n        if text is None:\n            return\n        before, center, after = self._partition()\n        log.completion.debug(\"Changing {} to '{}'\".format(center, text))\n        try:\n            maxsplit = cmdutils.cmd_dict[before[0]].maxsplit\n        except (KeyError, IndexError):\n            maxsplit = None\n        if maxsplit is None:\n            text = self._quote(text)\n        model = self._model()\n        if model.count() == 1 and config.val.completion.quick:\n            # If we only have one item, we want to apply it immediately\n            # and go on to the next part.\n            self._change_completed_part(text, before, after, immediate=True)\n            if maxsplit is not None and maxsplit < len(before):\n                # If we are quick-completing the part after maxsplit, don't\n                # keep offering completions (see issue #1519)\n                self._ignore_change = True\n        else:\n            log.completion.debug(\"Will ignore next completion update.\")\n            self._ignore_change = True\n            self._change_completed_part(text, before, after)\n\n    @pyqtSlot()\n    def schedule_completion_update(self):\n        \"\"\"Schedule updating/enabling completion.\n\n        For performance reasons we don't want to block here, instead we do this\n        in the background.\n        \"\"\"\n        if (self._cmd.cursorPosition() == self._last_cursor_pos and\n                self._cmd.text() == self._last_text):\n            log.completion.debug(\"Ignoring update because there were no \"\n                                 \"changes.\")\n        else:\n            log.completion.debug(\"Scheduling completion update.\")\n            self._timer.start()\n        self._last_cursor_pos = self._cmd.cursorPosition()\n        self._last_text = self._cmd.text()\n\n    @pyqtSlot()\n    def _update_completion(self):\n        \"\"\"Check if completions are available and activate them.\"\"\"\n        if self._ignore_change:\n            log.completion.debug(\"Ignoring completion update because \"\n                                 \"ignore_change is True.\")\n            self._ignore_change = False\n            return\n\n        completion = self.parent()\n\n        if self._cmd.prefix() != ':':\n            # This is a search or gibberish, so we don't need to complete\n            # anything (yet)\n            # FIXME complete searches\n            # https://github.com/qutebrowser/qutebrowser/issues/32\n            completion.set_model(None)\n            self._last_completion_func = None\n            return\n\n        before_cursor, pattern, after_cursor = self._partition()\n\n        log.completion.debug(\"Updating completion: {} {} {}\".format(\n            before_cursor, pattern, after_cursor))\n\n        pattern = pattern.strip(\"'\\\"\")\n        func = self._get_new_completion(before_cursor, pattern)\n\n        if func is None:\n            log.completion.debug('Clearing completion')\n            completion.set_model(None)\n            self._last_completion_func = None\n            return\n\n        if func != self._last_completion_func:\n            self._last_completion_func = func\n            args = (x for x in before_cursor[1:] if not x.startswith('-'))\n            with debug.log_time(log.completion,\n                    'Starting {} completion'.format(func.__name__)):\n                info = CompletionInfo(config=config.instance,\n                                      keyconf=config.key_instance)\n                model = func(*args, info=info)\n            with debug.log_time(log.completion, 'Set completion model'):\n                completion.set_model(model)\n\n        completion.set_pattern(pattern)\n\n    def _change_completed_part(self, newtext, before, after, immediate=False):\n        \"\"\"Change the part we're currently completing in the commandline.\n\n        Args:\n            text: The text to set (string) for the token under the cursor.\n            before: Commandline tokens before the token under the cursor.\n            after: Commandline tokens after the token under the cursor.\n            immediate: True if the text should be completed immediately\n                       including a trailing space and we shouldn't continue\n                       completing the current item.\n        \"\"\"\n        text = self._cmd.prefix() + ' '.join(before + [newtext])\n        pos = len(text) + (1 if immediate else 0)\n        if after:\n            text += ' ' + ' '.join(after)\n        elif immediate:\n            # pad with a space if quick-completing the last entry\n            text += ' '\n        log.completion.debug(\"setting text = '{}', pos = {}\".format(text, pos))\n        self._cmd.setText(text)\n        self._cmd.setCursorPosition(pos)\n        self._cmd.setFocus()\n        self._cmd.show_cmd.emit()\n", "path": "qutebrowser/completion/completer.py"}], "after_files": [{"content": "# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:\n\n# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>\n#\n# This file is part of qutebrowser.\n#\n# qutebrowser is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# qutebrowser is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with qutebrowser.  If not, see <http://www.gnu.org/licenses/>.\n\n\"\"\"Completer attached to a CompletionView.\"\"\"\n\nimport attr\nfrom PyQt5.QtCore import pyqtSlot, QObject, QTimer\n\nfrom qutebrowser.config import config\nfrom qutebrowser.commands import cmdutils, runners\nfrom qutebrowser.utils import log, utils, debug\nfrom qutebrowser.completion.models import miscmodels\n\n\n@attr.s\nclass CompletionInfo:\n\n    \"\"\"Context passed into all completion functions.\"\"\"\n\n    config = attr.ib()\n    keyconf = attr.ib()\n\n\nclass Completer(QObject):\n\n    \"\"\"Completer which manages completions in a CompletionView.\n\n    Attributes:\n        _cmd: The statusbar Command object this completer belongs to.\n        _ignore_change: Whether to ignore the next completion update.\n        _timer: The timer used to trigger the completion update.\n        _last_cursor_pos: The old cursor position so we avoid double completion\n                          updates.\n        _last_text: The old command text so we avoid double completion updates.\n        _last_completion_func: The completion function used for the last text.\n    \"\"\"\n\n    def __init__(self, cmd, parent=None):\n        super().__init__(parent)\n        self._cmd = cmd\n        self._ignore_change = False\n        self._timer = QTimer()\n        self._timer.setSingleShot(True)\n        self._timer.setInterval(0)\n        self._timer.timeout.connect(self._update_completion)\n        self._last_cursor_pos = None\n        self._last_text = None\n        self._last_completion_func = None\n        self._cmd.update_completion.connect(self.schedule_completion_update)\n\n    def __repr__(self):\n        return utils.get_repr(self)\n\n    def _model(self):\n        \"\"\"Convenience method to get the current completion model.\"\"\"\n        completion = self.parent()\n        return completion.model()\n\n    def _get_new_completion(self, before_cursor, under_cursor):\n        \"\"\"Get the completion function based on the current command text.\n\n        Args:\n            before_cursor: The command chunks before the cursor.\n            under_cursor: The command chunk under the cursor.\n\n        Return:\n            A completion model.\n        \"\"\"\n        if '--' in before_cursor or under_cursor.startswith('-'):\n            # cursor on a flag or after an explicit split (--)\n            return None\n        log.completion.debug(\"Before removing flags: {}\".format(before_cursor))\n        before_cursor = [x for x in before_cursor if not x.startswith('-')]\n        log.completion.debug(\"After removing flags: {}\".format(before_cursor))\n        if not before_cursor:\n            # '|' or 'set|'\n            log.completion.debug('Starting command completion')\n            return miscmodels.command\n        try:\n            cmd = cmdutils.cmd_dict[before_cursor[0]]\n        except KeyError:\n            log.completion.debug(\"No completion for unknown command: {}\"\n                                 .format(before_cursor[0]))\n            return None\n        argpos = len(before_cursor) - 1\n        try:\n            func = cmd.get_pos_arg_info(argpos).completion\n        except IndexError:\n            log.completion.debug(\"No completion in position {}\".format(argpos))\n            return None\n        return func\n\n    def _quote(self, s):\n        \"\"\"Quote s if it needs quoting for the commandline.\n\n        Note we don't use shlex.quote because that quotes a lot of shell\n        metachars we don't need to have quoted.\n        \"\"\"\n        if not s:\n            return \"''\"\n        elif any(c in s for c in ' \"\\'\\t\\n\\\\'):\n            # use single quotes, and put single quotes into double quotes\n            # the string $'b is then quoted as '$'\"'\"'b'\n            return \"'\" + s.replace(\"'\", \"'\\\"'\\\"'\") + \"'\"\n        else:\n            return s\n\n    def _partition(self):\n        \"\"\"Divide the commandline text into chunks around the cursor position.\n\n        Return:\n            ([parts_before_cursor], 'part_under_cursor', [parts_after_cursor])\n        \"\"\"\n        text = self._cmd.text()[len(self._cmd.prefix()):]\n        if not text or not text.strip():\n            # Only \":\", empty part under the cursor with nothing before/after\n            return [], '', []\n        parser = runners.CommandParser()\n        result = parser.parse(text, fallback=True, keep=True)\n        # pylint: disable=not-an-iterable\n        parts = [x for x in result.cmdline if x]\n        # pylint: enable=not-an-iterable\n        pos = self._cmd.cursorPosition() - len(self._cmd.prefix())\n        pos = min(pos, len(text))  # Qt treats 2-byte UTF-16 chars as 2 chars\n        log.completion.debug('partitioning {} around position {}'.format(parts,\n                                                                         pos))\n        for i, part in enumerate(parts):\n            pos -= len(part)\n            if pos <= 0:\n                if part[pos-1:pos+1].isspace():\n                    # cursor is in a space between two existing words\n                    parts.insert(i, '')\n                prefix = [x.strip() for x in parts[:i]]\n                center = parts[i].strip()\n                # strip trailing whitepsace included as a separate token\n                postfix = [x.strip() for x in parts[i+1:] if not x.isspace()]\n                log.completion.debug(\n                    \"partitioned: {} '{}' {}\".format(prefix, center, postfix))\n                return prefix, center, postfix\n\n        # We should always return above\n        assert False, parts\n\n    @pyqtSlot(str)\n    def on_selection_changed(self, text):\n        \"\"\"Change the completed part if a new item was selected.\n\n        Called from the views selectionChanged method.\n\n        Args:\n            text: Newly selected text.\n        \"\"\"\n        if text is None:\n            return\n        before, center, after = self._partition()\n        log.completion.debug(\"Changing {} to '{}'\".format(center, text))\n        try:\n            maxsplit = cmdutils.cmd_dict[before[0]].maxsplit\n        except (KeyError, IndexError):\n            maxsplit = None\n        if maxsplit is None:\n            text = self._quote(text)\n        model = self._model()\n        if model.count() == 1 and config.val.completion.quick:\n            # If we only have one item, we want to apply it immediately\n            # and go on to the next part.\n            self._change_completed_part(text, before, after, immediate=True)\n            if maxsplit is not None and maxsplit < len(before):\n                # If we are quick-completing the part after maxsplit, don't\n                # keep offering completions (see issue #1519)\n                self._ignore_change = True\n        else:\n            log.completion.debug(\"Will ignore next completion update.\")\n            self._ignore_change = True\n            self._change_completed_part(text, before, after)\n\n    @pyqtSlot()\n    def schedule_completion_update(self):\n        \"\"\"Schedule updating/enabling completion.\n\n        For performance reasons we don't want to block here, instead we do this\n        in the background.\n\n        We delay the update only if we've already input some text and ignore\n        updates if the text is shorter than completion.min_chars (unless we're\n        hitting backspace in which case updates won't be ignored).\n        \"\"\"\n        _cmd, _sep, rest = self._cmd.text().partition(' ')\n        input_length = len(rest)\n        if (0 < input_length < config.val.completion.min_chars and\n                self._cmd.cursorPosition() > self._last_cursor_pos):\n            log.completion.debug(\"Ignoring update because the length of \"\n                                 \"the text is less than completion.min_chars.\")\n        elif (self._cmd.cursorPosition() == self._last_cursor_pos and\n                self._cmd.text() == self._last_text):\n            log.completion.debug(\"Ignoring update because there were no \"\n                                 \"changes.\")\n        else:\n            log.completion.debug(\"Scheduling completion update.\")\n            start_delay = config.val.completion.delay if self._last_text else 0\n            self._timer.start(start_delay)\n        self._last_cursor_pos = self._cmd.cursorPosition()\n        self._last_text = self._cmd.text()\n\n    @pyqtSlot()\n    def _update_completion(self):\n        \"\"\"Check if completions are available and activate them.\"\"\"\n        if self._ignore_change:\n            log.completion.debug(\"Ignoring completion update because \"\n                                 \"ignore_change is True.\")\n            self._ignore_change = False\n            return\n\n        completion = self.parent()\n\n        if self._cmd.prefix() != ':':\n            # This is a search or gibberish, so we don't need to complete\n            # anything (yet)\n            # FIXME complete searches\n            # https://github.com/qutebrowser/qutebrowser/issues/32\n            completion.set_model(None)\n            self._last_completion_func = None\n            return\n\n        before_cursor, pattern, after_cursor = self._partition()\n\n        log.completion.debug(\"Updating completion: {} {} {}\".format(\n            before_cursor, pattern, after_cursor))\n\n        pattern = pattern.strip(\"'\\\"\")\n        func = self._get_new_completion(before_cursor, pattern)\n\n        if func is None:\n            log.completion.debug('Clearing completion')\n            completion.set_model(None)\n            self._last_completion_func = None\n            return\n\n        if func != self._last_completion_func:\n            self._last_completion_func = func\n            args = (x for x in before_cursor[1:] if not x.startswith('-'))\n            with debug.log_time(log.completion,\n                    'Starting {} completion'.format(func.__name__)):\n                info = CompletionInfo(config=config.instance,\n                                      keyconf=config.key_instance)\n                model = func(*args, info=info)\n            with debug.log_time(log.completion, 'Set completion model'):\n                completion.set_model(model)\n\n        completion.set_pattern(pattern)\n\n    def _change_completed_part(self, newtext, before, after, immediate=False):\n        \"\"\"Change the part we're currently completing in the commandline.\n\n        Args:\n            text: The text to set (string) for the token under the cursor.\n            before: Commandline tokens before the token under the cursor.\n            after: Commandline tokens after the token under the cursor.\n            immediate: True if the text should be completed immediately\n                       including a trailing space and we shouldn't continue\n                       completing the current item.\n        \"\"\"\n        text = self._cmd.prefix() + ' '.join(before + [newtext])\n        pos = len(text) + (1 if immediate else 0)\n        if after:\n            text += ' ' + ' '.join(after)\n        elif immediate:\n            # pad with a space if quick-completing the last entry\n            text += ' '\n        log.completion.debug(\"setting text = '{}', pos = {}\".format(text, pos))\n        self._cmd.setText(text)\n        self._cmd.setCursorPosition(pos)\n        self._cmd.setFocus()\n        self._cmd.show_cmd.emit()\n", "path": "qutebrowser/completion/completer.py"}]}