internlm/SWE-Fixer-Train-110K huggingface dataset:
Repository: qutebrowser/qutebrowser

Issue:
completion -> timestamp-format is not validated
This means users can do `:set completion timestamp-format %` or so and get `ValueError`s when trying to format the timestamps in the completion.



Discussion:

Resolving patch:
diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py
--- a/qutebrowser/config/configdata.py
+++ b/qutebrowser/config/configdata.py
@@ -381,7 +381,7 @@ def data(readonly=False):
              "What to display in the download filename input."),
 
             ('timestamp-format',
-             SettingValue(typ.String(none_ok=True), '%Y-%m-%d'),
+             SettingValue(typ.TimestampTemplate(none_ok=True), '%Y-%m-%d'),
              "How to format timestamps (e.g. for history)"),
 
             ('show',
diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py
--- a/qutebrowser/config/configtypes.py
+++ b/qutebrowser/config/configtypes.py
@@ -27,6 +27,7 @@
 import itertools
 import collections
 import warnings
+import datetime
 
 from PyQt5.QtCore import QUrl
 from PyQt5.QtGui import QColor, QFont
@@ -1630,3 +1631,25 @@ class URLSegmentList(FlagList):
     """A list of URL segments."""
 
     valid_values = ValidValues('host', 'path', 'query', 'anchor')
+
+
+class TimestampTemplate(BaseType):
+
+    """A strftime-like template for timestamps.
+
+    See
+    https://docs.python.org/3/library/datetime.html#strftime-strptime-behavior
+    for reference.
+    """
+
+    def validate(self, value):
+        self._basic_validation(value)
+        if not value:
+            return
+        try:
+            # Dummy check to see if the template is valid
+            datetime.datetime.now().strftime(value)
+        except ValueError as error:
+            # thrown on invalid template string
+            raise configexc.ValidationError(
+                value, "Invalid format string: {}".format(error))


Test patch:
diff --git a/tests/unit/config/test_configtypes.py b/tests/unit/config/test_configtypes.py
--- a/tests/unit/config/test_configtypes.py
+++ b/tests/unit/config/test_configtypes.py
@@ -2042,6 +2042,24 @@ def test_complete(self, klass):
         klass().complete()
 
 
+class TestTimestampTemplate:
+
+    """Test TimestampTemplate."""
+
+    @pytest.fixture
+    def klass(self):
+        return configtypes.TimestampTemplate
+
+    @pytest.mark.parametrize('val', ['', 'foobar', '%H:%M', 'foo %H bar %M'])
+    def test_validate_valid(self, klass, val):
+        klass(none_ok=True).validate(val)
+
+    @pytest.mark.parametrize('val', ['', '%'])
+    def test_validate_invalid(self, klass, val):
+        with pytest.raises(configexc.ValidationError):
+            klass().validate(val)
+
+
 @pytest.mark.parametrize('first, second, equal', [
     (re.compile('foo'), RegexEq('foo'), True),
     (RegexEq('bar'), re.compile('bar'), True),
Repository: qutebrowser/qutebrowser

Issue:
:tab-move +/- doesn't wrap
If `tabs -> wrap` is `true`, using `:tab-move +` and `:tab-move -` should wrap as well.



Discussion:
The source for this is in `tab_move` in `qutebrowser/browser/commands.py`, the related tests (which should be changed and maybe extended as well) in `tests/integration/features/tabs.feature`.


Resolving patch:
diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py
--- a/qutebrowser/browser/commands.py
+++ b/qutebrowser/browser/commands.py
@@ -900,6 +900,8 @@ def tab_move(self, direction: {'type': ('+', '-')}=None, count=None):
                 new_idx = self._current_index() - delta
             elif direction == '+':  # pragma: no branch
                 new_idx = self._current_index() + delta
+            if config.get('tabs', 'wrap'):
+                new_idx = new_idx % self._count()
         else:  # pragma: no cover
             raise ValueError("Invalid direction '{}'!".format(direction))
 


Test patch:
diff --git a/tests/integration/features/tabs.feature b/tests/integration/features/tabs.feature
--- a/tests/integration/features/tabs.feature
+++ b/tests/integration/features/tabs.feature
@@ -384,13 +384,37 @@ Feature: Tab management
             - data/numbers/2.txt
 
     Scenario: :tab-move with relative position and too big count.
-        When I open data/numbers/1.txt
+        When I set tabs -> wrap to false
+        And I open data/numbers/1.txt
         And I open data/numbers/2.txt in a new tab
         And I open data/numbers/3.txt in a new tab
         And I run :tab-focus 1
         And I run :tab-move + with count 3
         Then the error "Can't move tab to position 4!" should be shown
 
+    Scenario: :tab-move with relative position (positive) and wrap
+        When I set tabs -> wrap to true
+        And I open data/numbers/1.txt
+        And I open data/numbers/2.txt in a new tab
+        And I open data/numbers/3.txt in a new tab
+        And I run :tab-move +
+        Then the following tabs should be open:
+            - data/numbers/3.txt (active)
+            - data/numbers/1.txt
+            - data/numbers/2.txt
+
+    Scenario: :tab-move with relative position (negative), wrap and count
+        When I set tabs -> wrap to true
+        And I open data/numbers/1.txt
+        And I open data/numbers/2.txt in a new tab
+        And I open data/numbers/3.txt in a new tab
+        And I run :tab-focus 1
+        And I run :tab-move - with count 8
+        Then the following tabs should be open:
+            - data/numbers/2.txt
+            - data/numbers/1.txt (active)
+            - data/numbers/3.txt
+
     Scenario: Make sure :tab-move retains metadata
         When I open data/title.html
         And I open data/hello.txt in a new tab
Repository: qutebrowser/qutebrowser

Issue:
Open the current url in an editor
If I want to modify the current url, it might be nice to have a command that opens the current url in an editor and navigates to that url when the editor is closed. 

Think of something like visual mode when using vi mode for bash -- you press `v` and the current command line is opened in vi. When you close the editor, the command is run.

Right now, it seems like I have to press `O` (to fill the command line with the current url), backspace a lot, then retype most of the url.



Discussion:
FWIW, you can also use most [readline shortcuts](http://www.bigsmoke.us/readline/shortcuts) to navigate in the commandline.

Except for its [vi mode](http://tiswww.case.edu/php/chet/readline/readline.html#SEC22) of course...


Resolving patch:
diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py
--- a/qutebrowser/browser/commands.py
+++ b/qutebrowser/browser/commands.py
@@ -1847,3 +1847,26 @@ def debug_clear_ssl_errors(self):
         """Clear remembered SSL error answers."""
         nam = self._current_widget().page().networkAccessManager()
         nam.clear_all_ssl_errors()
+
+    @cmdutils.register(instance='command-dispatcher', scope='window')
+    def edit_url(self, url=None, bg=False, tab=False, window=False, count=None):
+        """Navigate to a url formed in an external editor.
+
+        The editor which should be launched can be configured via the
+        `general -> editor` config option.
+
+        Args:
+            url: URL to edit; defaults to the current page url.
+            bg: Open in a new background tab.
+            tab: Open in a new tab.
+            window: Open in a new window.
+            count: The tab index to open the URL in, or None.
+        """
+        ed = editor.ExternalEditor(self._win_id, self._tabbed_browser)
+
+        # Passthrough for openurl args (e.g. -t, -b, -w)
+        ed.editing_finished.connect(
+            functools.partial(self.openurl,
+            bg=bg, tab=tab, window=window, count=count))
+
+        ed.edit(url or self._current_url().toString())


Test patch:
Repository: qutebrowser/qutebrowser

Issue:
:navigate should strip #fragments from the URLs
When I open e.g. `http://example.com/foo/bar#baz` and run `:navigate up`, the new URL is `http://example.com/foo#baz`. The fragment should be thrown away, there is no reason to assume that the new page will contain the same anchor.
The same probably applies also to other navigate actions: prev, next, increment, decrement.



Discussion:
Agreed!


Resolving patch:
diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py
--- a/qutebrowser/browser/commands.py
+++ b/qutebrowser/browser/commands.py
@@ -428,6 +428,8 @@ def _navigate_incdec(self, url, incdec, tab, background, window):
             new_url = urlutils.incdec_number(url, incdec, segments=segments)
         except urlutils.IncDecError as error:
             raise cmdexc.CommandError(error.msg)
+        url.setFragment(None)
+
         self._open(new_url, tab, background, window)
 
     def _navigate_up(self, url, tab, background, window):
@@ -444,6 +446,7 @@ def _navigate_up(self, url, tab, background, window):
             raise cmdexc.CommandError("Can't go up!")
         new_path = posixpath.join(path, posixpath.pardir)
         url.setPath(new_path)
+        url.setFragment(None)
         self._open(url, tab, background, window)
 
     @cmdutils.register(instance='command-dispatcher', scope='window')


Test patch:
diff --git a/tests/integration/features/navigate.feature b/tests/integration/features/navigate.feature
--- a/tests/integration/features/navigate.feature
+++ b/tests/integration/features/navigate.feature
@@ -38,6 +38,11 @@ Feature: Using :navigate
         And I run :navigate next
         Then the error "No forward links found!" should be shown
 
+    Scenario: Navigating to next page to a fragment
+        When I open data/navigate#fragment
+        And I run :navigate next
+        Then data/navigate/next.html should be loaded
+
     # increment/decrement
 
     Scenario: Incrementing number in URL
Repository: qutebrowser/qutebrowser

Issue:
Tab gets deleted with last-close = default-page when doing :undo
When starting `qutebrowser --temp-basedir -s tabs last-close default-page`, pressing `u` will produce a window without any tabs open.

Doing `u` again will crash:

```
Traceback (most recent call last):
  [...]
  File "/home/florian/proj/qutebrowser/git/qutebrowser/browser/commands.py", line 767, in undo
    self._tabbed_browser.undo()
  File "/home/florian/proj/qutebrowser/git/qutebrowser/mainwindow/tabbedbrowser.py", line 310, in undo
    no_history = self.widget(0).history().count() == 1
AttributeError: 'NoneType' object has no attribute 'history'
```



Discussion:

Resolving patch:
diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py
--- a/qutebrowser/mainwindow/tabbedbrowser.py
+++ b/qutebrowser/mainwindow/tabbedbrowser.py
@@ -305,6 +305,7 @@ def undo(self):
         """Undo removing of a tab."""
         # Remove unused tab which may be created after the last tab is closed
         last_close = config.get('tabs', 'last-close')
+        use_current_tab = False
         if last_close in ['blank', 'startpage', 'default-page']:
             only_one_tab_open = self.count() == 1
             no_history = self.widget(0).history().count() == 1
@@ -317,12 +318,17 @@ def undo(self):
             last_close_urlstr = urls[last_close].toString().rstrip('/')
             first_tab_urlstr = first_tab_url.toString().rstrip('/')
             last_close_url_used = first_tab_urlstr == last_close_urlstr
-
-            if only_one_tab_open and no_history and last_close_url_used:
-                self.removeTab(0)
+            use_current_tab = (only_one_tab_open and no_history and
+                               last_close_url_used)
 
         url, history_data = self._undo_stack.pop()
-        newtab = self.tabopen(url, background=False)
+
+        if use_current_tab:
+            self.openurl(url, newtab=False)
+            newtab = self.widget(0)
+        else:
+            newtab = self.tabopen(url, background=False)
+
         qtutils.deserialize(history_data, newtab.history())
 
     @pyqtSlot('QUrl', bool)


Test patch:
diff --git a/tests/integration/features/tabs.feature b/tests/integration/features/tabs.feature
--- a/tests/integration/features/tabs.feature
+++ b/tests/integration/features/tabs.feature
@@ -635,6 +635,16 @@ Feature: Tab management
         Then the following tabs should be open:
             - data/hello.txt (active)
 
+    Scenario: Double-undo with single tab on last-close default page
+        Given I have a fresh instance
+        When I open about:blank
+        And I set tabs -> last-close to default-page
+        And I set general -> default-page to about:blank
+        And I run :undo
+        And I run :undo
+        Then the error "Nothing to undo!" should be shown
+        And the error "Nothing to undo!" should be shown
+
     # last-close
 
     Scenario: last-close = blank
Repository: qutebrowser/qutebrowser

Issue:
Better support for special keys
Currently special keys (e.g. `<Ctrl-x>`) can only be used standalone.

Ideally they should be usable with a count, and inside of a keychain (e.g. `g<ctrl-g>` or whatever).



Discussion:

Resolving patch:
diff --git a/qutebrowser/keyinput/basekeyparser.py b/qutebrowser/keyinput/basekeyparser.py
--- a/qutebrowser/keyinput/basekeyparser.py
+++ b/qutebrowser/keyinput/basekeyparser.py
@@ -127,7 +127,9 @@ def _handle_special_key(self, e):
         except KeyError:
             self._debug_log("No binding found for {}.".format(binding))
             return False
-        self.execute(cmdstr, self.Type.special)
+        count, _ = self._split_count()
+        self.execute(cmdstr, self.Type.special, count)
+        self.clear_keystring()
         return True
 
     def _split_count(self):


Test patch:
diff --git a/tests/unit/keyinput/test_basekeyparser.py b/tests/unit/keyinput/test_basekeyparser.py
--- a/tests/unit/keyinput/test_basekeyparser.py
+++ b/tests/unit/keyinput/test_basekeyparser.py
@@ -183,7 +183,17 @@ def test_valid_key(self, fake_keyevent_factory, keyparser):
         keyparser.handle(fake_keyevent_factory(Qt.Key_A, modifier))
         keyparser.handle(fake_keyevent_factory(Qt.Key_X, modifier))
         keyparser.execute.assert_called_once_with(
-            'ctrla', keyparser.Type.special)
+            'ctrla', keyparser.Type.special, None)
+
+    def test_valid_key_count(self, fake_keyevent_factory, keyparser):
+        if sys.platform == 'darwin':
+            modifier = Qt.MetaModifier
+        else:
+            modifier = Qt.ControlModifier
+        keyparser.handle(fake_keyevent_factory(5, text='5'))
+        keyparser.handle(fake_keyevent_factory(Qt.Key_A, modifier, text='A'))
+        keyparser.execute.assert_called_once_with(
+            'ctrla', keyparser.Type.special, 5)
 
     def test_invalid_key(self, fake_keyevent_factory, keyparser):
         keyparser.handle(fake_keyevent_factory(
@@ -217,7 +227,7 @@ def test_valid_special_key(self, fake_keyevent_factory, keyparser):
         keyparser.handle(fake_keyevent_factory(Qt.Key_A, modifier))
         keyparser.handle(fake_keyevent_factory(Qt.Key_X, modifier))
         keyparser.execute.assert_called_once_with(
-            'ctrla', keyparser.Type.special)
+            'ctrla', keyparser.Type.special, None)
         assert keyparser._keystring == ''
 
     def test_invalid_special_key(self, fake_keyevent_factory, keyparser):
Repository: qutebrowser/qutebrowser

Issue:
QtValueError on invalid URL when e.g. searching (due to marks)
When doing `/foo` quickly after qutebrowser started and is loading a page, this happens:

```
Traceback (most recent call last):
  File "/home/florian/proj/qutebrowser/git/qutebrowser/commands/runners.py", line 265, in run_safely
    self.run(text, count)
  File "/home/florian/proj/qutebrowser/git/qutebrowser/commands/runners.py", line 258, in run
    result.cmd.run(self._win_id, args)
  File "/home/florian/proj/qutebrowser/git/qutebrowser/commands/command.py", line 505, in run
    self.handler(*posargs, **kwargs)
  File "/home/florian/proj/qutebrowser/git/qutebrowser/browser/commands.py", line 1432, in search
    self.set_mark("'")
  File "/home/florian/proj/qutebrowser/git/qutebrowser/browser/commands.py", line 1911, in set_mark
    self._tabbed_browser.set_mark(key)
  File "/home/florian/proj/qutebrowser/git/qutebrowser/mainwindow/tabbedbrowser.py", line 659, in set_mark
    url = self.current_url().adjusted(QUrl.RemoveFragment)
  File "/home/florian/proj/qutebrowser/git/qutebrowser/mainwindow/tabbedbrowser.py", line 239, in current_url
    qtutils.ensure_valid(url)
  File "/home/florian/proj/qutebrowser/git/qutebrowser/utils/qtutils.py", line 145, in ensure_valid
    raise QtValueError(obj)
qutebrowser.utils.qtutils.QtValueError: PyQt5.QtCore.QUrl('') is not valid
```

@rcorre can you take a look at this? I'd propose to show an error when trying to set a mark on an invalid URL, and silently ignore it when setting the `'` mark (as that gets set automatically a lot)



Discussion:

Resolving patch:
diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py
--- a/qutebrowser/mainwindow/tabbedbrowser.py
+++ b/qutebrowser/mainwindow/tabbedbrowser.py
@@ -656,7 +656,13 @@ def set_mark(self, key):
             key: mark identifier; capital indicates a global mark
         """
         # strip the fragment as it may interfere with scrolling
-        url = self.current_url().adjusted(QUrl.RemoveFragment)
+        try:
+            url = self.current_url().adjusted(QUrl.RemoveFragment)
+        except qtutils.QtValueError:
+            # show an error only if the mark is not automatically set
+            if key != "'":
+                message.error(self._win_id, "Failed to set mark: url invalid")
+            return
         point = self.currentWidget().page().currentFrame().scrollPosition()
 
         if key.isupper():


Test patch:
Repository: qutebrowser/qutebrowser

Issue:
confirm-quit = downloads acts weird
When having a single, _finished_ download and quitting, qutebrowser says there are 2 running downloads.

The check simply uses `download_count = download_manager.rowCount()` which might do the wrong thing.

/cc @peterlvilim 



Discussion:
I'm getting a segfault when setting `ui.confirm-quit=downloads` and quitting while I'm downloading something :o

I don't have it with `--temp`, will investigate later ... But I did notice this in `--debug`, which seems to be the problem:

```
21:49:38 DEBUG    statusbar  prompter:ask_question:304 Asking question <qutebrowser.utils.usertypes.Question default=True mode=<PromptMode.yesno: 1> text='Really quit?\n1 download is running.'>, blocking True, loops [], queue deque([])
21:49:38 DEBUG    statusbar  prompter:ask_question:310 Ignoring question because we're shutting down.
```

Meh. Starting with `catchsegv` and checking the python stacktrace when you run qutebrowser after the segfault might help in checking what's happening.


Resolving patch:
diff --git a/qutebrowser/browser/downloads.py b/qutebrowser/browser/downloads.py
--- a/qutebrowser/browser/downloads.py
+++ b/qutebrowser/browser/downloads.py
@@ -897,8 +897,8 @@ def fetch(self, reply, *, fileobj=None, filename=None, auto_remove=False,
         download.redirected.connect(
             functools.partial(self.on_redirect, download))
         download.basename = suggested_filename
-        idx = len(self.downloads) + 1
-        download.index = idx
+        idx = len(self.downloads)
+        download.index = idx + 1  # "Human readable" index
         self.beginInsertRows(QModelIndex(), idx, idx)
         self.downloads.append(download)
         self.endInsertRows()
@@ -1238,3 +1238,11 @@ def rowCount(self, parent=QModelIndex()):
             # We don't have children
             return 0
         return len(self.downloads)
+
+    def running_downloads(self):
+        """Return the amount of still running downloads.
+
+        Return:
+            The number of unfinished downloads.
+        """
+        return sum(1 for download in self.downloads if not download.done)
diff --git a/qutebrowser/mainwindow/mainwindow.py b/qutebrowser/mainwindow/mainwindow.py
--- a/qutebrowser/mainwindow/mainwindow.py
+++ b/qutebrowser/mainwindow/mainwindow.py
@@ -413,7 +413,7 @@ def closeEvent(self, e):
         tab_count = self.tabbed_browser.count()
         download_manager = objreg.get('download-manager', scope='window',
                                       window=self.win_id)
-        download_count = download_manager.rowCount()
+        download_count = download_manager.running_downloads()
         quit_texts = []
         # Ask if multiple-tabs are open
         if 'multiple-tabs' in confirm_quit and tab_count > 1:


Test patch:
Repository: qutebrowser/qutebrowser

Issue:
Show keybinding for commands in command completion
We could use the "misc" column in the command completion model to show the configured keybindings.
The idea was suggested by `konubinix` on IRC.

This might be more difficult now that we have arguments for commands, but it'd still be possible to at least show some overview.



Discussion:

Resolving patch:
diff --git a/qutebrowser/completion/models/instances.py b/qutebrowser/completion/models/instances.py
--- a/qutebrowser/completion/models/instances.py
+++ b/qutebrowser/completion/models/instances.py
@@ -186,3 +186,7 @@ def init():
     history = objreg.get('web-history')
     history.async_read_done.connect(
         functools.partial(update, [usertypes.Completion.url]))
+
+    keyconf = objreg.get('key-config')
+    keyconf.changed.connect(
+        functools.partial(update, [usertypes.Completion.command]))
diff --git a/qutebrowser/completion/models/miscmodels.py b/qutebrowser/completion/models/miscmodels.py
--- a/qutebrowser/completion/models/miscmodels.py
+++ b/qutebrowser/completion/models/miscmodels.py
@@ -19,6 +19,7 @@
 
 """Misc. CompletionModels."""
 
+from collections import defaultdict
 from PyQt5.QtCore import Qt, QTimer, pyqtSlot
 
 from qutebrowser.browser import webview
@@ -35,6 +36,8 @@ class CommandCompletionModel(base.BaseCompletionModel):
     # https://github.com/The-Compiler/qutebrowser/issues/545
     # pylint: disable=abstract-method
 
+    COLUMN_WIDTHS = (20, 60, 20)
+
     def __init__(self, parent=None):
         super().__init__(parent)
         assert cmdutils.cmd_dict
@@ -48,8 +51,18 @@ def __init__(self, parent=None):
         for name, cmd in config.section('aliases').items():
             cmdlist.append((name, "Alias for '{}'".format(cmd)))
         cat = self.new_category("Commands")
+
+        # map each command to its bound keys and show these in the misc column
+        keyconf = objreg.get('key-config')
+        cmd_to_keys = defaultdict(list)
+        for key, cmd in keyconf.get_bindings_for('normal').items():
+            # put special bindings last
+            if key.startswith('<') and key.endswith('>'):
+                cmd_to_keys[cmd].append(key)
+            else:
+                cmd_to_keys[cmd].insert(0, key)
         for (name, desc) in sorted(cmdlist):
-            self.new_item(cat, name, desc)
+            self.new_item(cat, name, desc, ', '.join(cmd_to_keys[name]))
 
 
 class HelpCompletionModel(base.BaseCompletionModel):


Test patch: