From f43a5973705602793b256b6e39bfccc394e5f9f1 Mon Sep 17 00:00:00 2001 From: Luca Benci Date: Thu, 12 Oct 2017 22:42:40 +0200 Subject: [PATCH 01/89] Add `cursor_position` to `serialize_elem` output --- qutebrowser/javascript/webelem.js | 1 + 1 file changed, 1 insertion(+) diff --git a/qutebrowser/javascript/webelem.js b/qutebrowser/javascript/webelem.js index ce348c46b..a37fcd6c8 100644 --- a/qutebrowser/javascript/webelem.js +++ b/qutebrowser/javascript/webelem.js @@ -53,6 +53,7 @@ window._qutebrowser.webelem = (function() { "value": elem.value, "outer_xml": elem.outerHTML, "rects": [], // Gets filled up later + "caret_position": elem.selectionStart, }; // https://github.com/qutebrowser/qutebrowser/issues/2569 From 67e41af8754a26cd84923285ac38f611deb215e2 Mon Sep 17 00:00:00 2001 From: Luca Benci Date: Thu, 12 Oct 2017 22:43:06 +0200 Subject: [PATCH 02/89] Add sanity check and accessor for `caret_position` --- qutebrowser/browser/webengine/webengineelem.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/qutebrowser/browser/webengine/webengineelem.py b/qutebrowser/browser/webengine/webengineelem.py index c294bebef..522605bba 100644 --- a/qutebrowser/browser/webengine/webengineelem.py +++ b/qutebrowser/browser/webengine/webengineelem.py @@ -47,6 +47,7 @@ class WebEngineElement(webelem.AbstractWebElement): 'class_name': str, 'rects': list, 'attributes': dict, + 'caret_position': int, } assert set(js_dict.keys()).issubset(js_dict_types.keys()) for name, typ in js_dict_types.items(): @@ -132,6 +133,9 @@ class WebEngineElement(webelem.AbstractWebElement): def set_value(self, value): self._js_call('set_value', value) + def caret_position(self): + return self._js_dict.get('caret_position', 0) + def insert_text(self, text): if not self.is_editable(strict=True): raise webelem.Error("Element is not editable!") From cdf4f692518f4c35f23c359d3bad715ea030ae00 Mon Sep 17 00:00:00 2001 From: Luca Benci Date: Thu, 12 Oct 2017 22:43:31 +0200 Subject: [PATCH 03/89] Pass `caret_position` to editor's `edit()` --- qutebrowser/browser/commands.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 7a8825720..a8400954e 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1594,10 +1594,12 @@ class CommandDispatcher: return assert isinstance(text, str), text + caret_position = elem.caret_position() + ed = editor.ExternalEditor(self._tabbed_browser) ed.editing_finished.connect(functools.partial( self.on_editing_finished, elem)) - ed.edit(text) + ed.edit(text, caret_position) @cmdutils.register(instance='command-dispatcher', hide=True, scope='window') From 6425061b3ad18354e0c59d78d39003d90132f43a Mon Sep 17 00:00:00 2001 From: Luca Benci Date: Thu, 12 Oct 2017 22:46:05 +0200 Subject: [PATCH 04/89] Substitute new `editor.command` placeholders Added placeholders are: * `{file}` has the same function as `{}` * `{line}` is the 1-based line number * `{column}` is the 1-based column number * `{line0}` like `{line}`, but 0-based * `{column0}` like `{column}` but 0-based --- qutebrowser/misc/editor.py | 62 +++++++++++++++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 5 deletions(-) diff --git a/qutebrowser/misc/editor.py b/qutebrowser/misc/editor.py index 3686e028f..54dd736d3 100644 --- a/qutebrowser/misc/editor.py +++ b/qutebrowser/misc/editor.py @@ -96,11 +96,12 @@ class ExternalEditor(QObject): def on_proc_error(self, _err): self._cleanup() - def edit(self, text): + def edit(self, text, caret_position): """Edit a given text. Args: text: The initial text to edit. + caret_position: The position of the caret in the text. """ if self._filename is not None: raise ValueError("Already editing a file!") @@ -121,7 +122,32 @@ class ExternalEditor(QObject): return self._remove_file = True - self._start_editor() + + # Here we calculate the line and column of the caret based on its + # position and the given text. + # + # NOTE: Both line and column are 1-based indexes, because that's what + # most editors use as line and column starting index. + # By "most" we mean at least vim, nvim, gvim, emacs, atom, sublimetext, + # notepad++, brackets, visual studio, QtCreator and so on. + # + # To find the line we just count how many newlines there are before + # the caret and add 1. + # + # To find the column we calculate the difference between the caret and + # the last newline before the caret. + # + # For example in the text `aaa\nbb|bbb` (| represents the caret): + # caret_position = 6 + # text[:caret_position] = `aaa\nbb` + # text[:caret_psotion].count('\n') = 1 + # caret_position - text[:caret_position].rfind('\n') = 3 + # + # Thus line, column = 2, 3, and the caret is indeed in the second + # line, third column + line = text[:caret_position].count('\n') + 1 + column = caret_position - text[:caret_position].rfind('\n') + self._start_editor(line=line, column=column) def edit_file(self, filename): """Edit the file with the given filename.""" @@ -129,13 +155,39 @@ class ExternalEditor(QObject): self._remove_file = False self._start_editor() - def _start_editor(self): - """Start the editor with the file opened as self._filename.""" + def _start_editor(self, line=1, column=1): + """Start the editor with the file opened as self._filename. + + Args: + caret_position: The position of the caret in the text. + """ self._proc = guiprocess.GUIProcess(what='editor', parent=self) self._proc.finished.connect(self.on_proc_closed) self._proc.error.connect(self.on_proc_error) editor = config.val.editor.command executable = editor[0] - args = [arg.replace('{}', self._filename) for arg in editor[1:]] + + args = [self._sub_placeholder(arg, line, column) for arg in editor[1:]] log.procs.debug("Calling \"{}\" with args {}".format(executable, args)) self._proc.start(executable, args) + + def _sub_placeholder(self, possible_placeholder, line, column): + """Substitute a single placeholder. + + The input to this function is not guaranteed to be a valid or known + placeholder. In this case the return value is the unchanged input. + + Args: + possible_placeholder: an argument of editor.command. + + Return: + The substituted placeholder or the original argument + """ + sub = possible_placeholder\ + .replace('{}', self._filename)\ + .replace('{file}', self._filename)\ + .replace('{line}', str(line))\ + .replace('{line0}', str(line-1))\ + .replace('{column}', str(column))\ + .replace('{column0}', str(column-1)) + return sub From ad9ac2191bc1a06a8dbbcdf8e3c24ca6d684554d Mon Sep 17 00:00:00 2001 From: Luca Benci Date: Thu, 12 Oct 2017 23:48:49 +0200 Subject: [PATCH 05/89] Also accept `{file}` placeholder --- qutebrowser/config/configtypes.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index da77401ac..203c4e573 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -1341,9 +1341,12 @@ class ShellCommand(List): if not value: return value - if self.placeholder and '{}' not in ' '.join(value): + if (self.placeholder and + '{}' not in ' '.join(value) and + '{file}' not in ' '.join(value)): raise configexc.ValidationError(value, "needs to contain a " - "{}-placeholder.") + "{}-placeholder or a " + "{file}-placeholder.") return value From 440740d30bf84a242c255ad987a3364f9071e069 Mon Sep 17 00:00:00 2001 From: Luca Benci Date: Fri, 13 Oct 2017 20:40:08 +0200 Subject: [PATCH 06/89] Don't crash when opening editor under webkit --- qutebrowser/browser/webkit/webkitelem.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/qutebrowser/browser/webkit/webkitelem.py b/qutebrowser/browser/webkit/webkitelem.py index 54dd6519d..e130e4623 100644 --- a/qutebrowser/browser/webkit/webkitelem.py +++ b/qutebrowser/browser/webkit/webkitelem.py @@ -126,6 +126,14 @@ class WebKitElement(webelem.AbstractWebElement): value = javascript.string_escape(value) self._elem.evaluateJavaScript("this.value='{}'".format(value)) + def caret_position(self): + self._check_vanished() + pos = self._elem.evaluateJavaScript('this.selectionStart') + assert isinstance(pos, (int, float, type(None))) + if pos is None: + return 0 + return int(pos) + def insert_text(self, text): self._check_vanished() if not self.is_editable(strict=True): From 08b562ea0c52e6dfa496055371ed668777adc418 Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Sat, 14 Oct 2017 17:49:27 -0400 Subject: [PATCH 07/89] Add caching for tab sizes --- qutebrowser/mainwindow/tabwidget.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py index c5566f877..55bd76f79 100644 --- a/qutebrowser/mainwindow/tabwidget.py +++ b/qutebrowser/mainwindow/tabwidget.py @@ -424,7 +424,7 @@ class TabBar(QTabBar): return super().mousePressEvent(e) - def minimumTabSizeHint(self, index, ellipsis: bool = True): + def minimumTabSizeHint(self, index, ellipsis: bool = True) -> QSize: """Set the minimum tab size to indicator/icon/... text. Args: @@ -434,11 +434,18 @@ class TabBar(QTabBar): Return: A QSize of the smallest tab size we can make. """ - text = '\u2026' if ellipsis else self.tabText(index) + return self.__minimumTabSizeHintHelper(self.tabText(index), + self.tabIcon(index), ellipsis) + + @functools.lru_cache(maxsize=100) + def __minimumTabSizeHintHelper(self, tab_text: str, + icon, + ellipsis: bool) -> QSize: + """Helper function to cache tab results.""" + text = '\u2026' if ellipsis else tab_text # Don't ever shorten if text is shorter than the ellipsis text_width = min(self.fontMetrics().width(text), - self.fontMetrics().width(self.tabText(index))) - icon = self.tabIcon(index) + self.fontMetrics().width(tab_text)) padding = config.val.tabs.padding indicator_padding = config.val.tabs.indicator_padding padding_h = padding.left + padding.right From 31bbc8c5b39abfe7f5d0a28df94b821d6055baa7 Mon Sep 17 00:00:00 2001 From: Ryan Farley Date: Thu, 14 Sep 2017 09:50:52 -0500 Subject: [PATCH 08/89] importer support for keywords and search engines This allows importer.py to process Netscape HTML exports from Firefox (and other Mozilla browsers) with three distinct types: * bookmarks (sans shortcuturl attribute) * keywords (bookmarks with a shortcuturl attribute) * searches (keywords with a URL containing a %s substitution) The first two can be combined at will in either quickmark or bookmark output formats, the only difference being that keywords will be used in place of titles when exporting to quickmark format. Searches are exported to qutebrowser.conf format, or the new config.py format. Dictionaries are used in the import function for readability's sake, but the command line arguments follow the same general formula of true-false flags used to select input bookmark types and the output format. --- scripts/importer.py | 87 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 73 insertions(+), 14 deletions(-) diff --git a/scripts/importer.py b/scripts/importer.py index 1b3be4d32..d140fa2a7 100755 --- a/scripts/importer.py +++ b/scripts/importer.py @@ -31,8 +31,30 @@ import argparse def main(): args = get_args() + bookmark_types = [] + output_format = '' + if args.search_query or args.search_output: + bookmark_types = ['search'] + if args.newconfig: + output_format = 'ncsearch' + else: + output_format = 'search' + else: + if args.bookmark_output: + output_format = 'bookmark' + elif args.quickmark_output: + output_format = 'quickmark' + if args.bookmark_query: + bookmark_types.append('bookmark') + if args.keyword_query: + bookmark_types.append('keyword') + if not bookmark_types: + bookmark_types = ['bookmark','keyword'] + if not output_format: + output_format = 'quickmark' + if args.browser in ['chromium', 'firefox', 'ie']: - import_netscape_bookmarks(args.bookmarks, args.bookmark_format) + import_netscape_bookmarks(args.bookmarks,bookmark_types,output_format) def get_args(): @@ -45,14 +67,31 @@ def get_args(): choices=['chromium', 'firefox', 'ie'], metavar='browser') parser.add_argument('-b', help="Output in bookmark format.", - dest='bookmark_format', action='store_true', + dest='bookmark_output', action='store_true', default=False, required=False) + parser.add_argument('-q', help="Output in quickmark format (default).", + dest='quickmark_output', action='store_true', + default=False,required=False) + parser.add_argument('-s', help="Output search engine format", + dest='search_output', action='store_true', + default=False,required=False) + parser.add_argument('--newconfig', help="Output search engine format for new config.py format", + default=False,action='store_true',required=False) + parser.add_argument('-S', help="Import search engines", + dest='search_query', action='store_true', + default=False,required=False) + parser.add_argument('-B', help="Import plain bookmarks (no keywords)", + dest='bookmark_query', action='store_true', + default=False,required=False) + parser.add_argument('-K', help="Import keywords (no search)", + dest='keyword_query', action='store_true', + default=False,required=False) parser.add_argument('bookmarks', help="Bookmarks file (html format)") args = parser.parse_args() return args -def import_netscape_bookmarks(bookmarks_file, is_bookmark_format): +def import_netscape_bookmarks(bookmarks_file, bookmark_types, output_format): """Import bookmarks from a NETSCAPE-Bookmark-file v1. Generated by Chromium, Firefox, IE and possibly more browsers @@ -60,18 +99,38 @@ def import_netscape_bookmarks(bookmarks_file, is_bookmark_format): import bs4 with open(bookmarks_file, encoding='utf-8') as f: soup = bs4.BeautifulSoup(f, 'html.parser') - - html_tags = soup.findAll('a') - if is_bookmark_format: - output_template = '{tag[href]} {tag.string}' - else: - output_template = '{tag.string} {tag[href]}' - + bookmark_query = { + 'search': + lambda tag: (tag.name == 'a') and ('shortcuturl' in tag.attrs) and ('%s' in tag['href']), + 'keyword': + lambda tag: (tag.name == 'a') and ('shortcuturl' in tag.attrs) and ('%s' not in tag['href']), + 'bookmark': + lambda tag: (tag.name == 'a') and ('shortcuturl' not in tag.attrs) and (tag.string) + } + output_template = { + 'ncsearch': { + 'search': "config.val.url.searchengines['{tag[shortcuturl]}'] = '{tag[href]}' #{tag.string}" + }, + 'search': { + 'search': '{tag[shortcuturl]} = {tag[href]} #{tag.string}', + }, + 'bookmark': { + 'bookmark': '{tag[href]} {tag.string}', + 'keyword': '{tag[href]} {tag.string}' + }, + 'quickmark': { + 'bookmark': '{tag.string} {tag[href]}', + 'keyword': '{tag[shortcuturl]} {tag[href]}' + } + } bookmarks = [] - for tag in html_tags: - if tag['href'] not in bookmarks: - bookmarks.append(output_template.format(tag=tag)) - + for typ in bookmark_types: + tags = soup.findAll(bookmark_query[typ]) + for tag in tags: + if typ=='search': + tag['href'] = tag['href'].replace('%s','{}') + if tag['href'] not in bookmarks: + bookmarks.append(output_template[output_format][typ].format(tag=tag)) for bookmark in bookmarks: print(bookmark) From c163f702c2365398a394755eb7dc6ba1dec51060 Mon Sep 17 00:00:00 2001 From: Ryan Farley Date: Fri, 15 Sep 2017 03:03:24 -0500 Subject: [PATCH 09/89] fix config.val in format --- scripts/importer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/importer.py b/scripts/importer.py index d140fa2a7..d22bc14f3 100755 --- a/scripts/importer.py +++ b/scripts/importer.py @@ -109,7 +109,7 @@ def import_netscape_bookmarks(bookmarks_file, bookmark_types, output_format): } output_template = { 'ncsearch': { - 'search': "config.val.url.searchengines['{tag[shortcuturl]}'] = '{tag[href]}' #{tag.string}" + 'search': "c.url.searchengines['{tag[shortcuturl]}'] = '{tag[href]}' #{tag.string}" }, 'search': { 'search': '{tag[shortcuturl]} = {tag[href]} #{tag.string}', From 898dde566d09eef010cbeb597ef0d2815f8c149c Mon Sep 17 00:00:00 2001 From: Ryan Farley Date: Fri, 15 Sep 2017 03:37:16 -0500 Subject: [PATCH 10/89] fix whitespace issues --- scripts/importer.py | 117 +++++++++++++++++++++++++++++--------------- 1 file changed, 77 insertions(+), 40 deletions(-) diff --git a/scripts/importer.py b/scripts/importer.py index d22bc14f3..aa53bccce 100755 --- a/scripts/importer.py +++ b/scripts/importer.py @@ -18,14 +18,11 @@ # # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . - - """Tool to import data from other browsers. Currently only importing bookmarks from Netscape Bookmark files is supported. """ - import argparse @@ -49,43 +46,74 @@ def main(): if args.keyword_query: bookmark_types.append('keyword') if not bookmark_types: - bookmark_types = ['bookmark','keyword'] + bookmark_types = ['bookmark', 'keyword'] if not output_format: output_format = 'quickmark' if args.browser in ['chromium', 'firefox', 'ie']: - import_netscape_bookmarks(args.bookmarks,bookmark_types,output_format) + import_netscape_bookmarks(args.bookmarks, bookmark_types, + output_format) def get_args(): """Get the argparse parser.""" parser = argparse.ArgumentParser( epilog="To import bookmarks from Chromium, Firefox or IE, " - "export them to HTML in your browsers bookmark manager. " - "By default, this script will output in a quickmarks format.") - parser.add_argument('browser', help="Which browser? (chromium, firefox)", - choices=['chromium', 'firefox', 'ie'], - metavar='browser') - parser.add_argument('-b', help="Output in bookmark format.", - dest='bookmark_output', action='store_true', - default=False, required=False) - parser.add_argument('-q', help="Output in quickmark format (default).", - dest='quickmark_output', action='store_true', - default=False,required=False) - parser.add_argument('-s', help="Output search engine format", - dest='search_output', action='store_true', - default=False,required=False) - parser.add_argument('--newconfig', help="Output search engine format for new config.py format", - default=False,action='store_true',required=False) - parser.add_argument('-S', help="Import search engines", - dest='search_query', action='store_true', - default=False,required=False) - parser.add_argument('-B', help="Import plain bookmarks (no keywords)", - dest='bookmark_query', action='store_true', - default=False,required=False) - parser.add_argument('-K', help="Import keywords (no search)", - dest='keyword_query', action='store_true', - default=False,required=False) + "export them to HTML in your browsers bookmark manager. " + "By default, this script will output in a quickmarks format.") + parser.add_argument( + 'browser', + help="Which browser? (chromium, firefox)", + choices=['chromium', 'firefox', 'ie'], + metavar='browser') + parser.add_argument( + '-b', + help="Output in bookmark format.", + dest='bookmark_output', + action='store_true', + default=False, + required=False) + parser.add_argument( + '-q', + help="Output in quickmark format (default).", + dest='quickmark_output', + action='store_true', + default=False, + required=False) + parser.add_argument( + '-s', + help="Output search engine format", + dest='search_output', + action='store_true', + default=False, + required=False) + parser.add_argument( + '--newconfig', + help="Output search engine format for new config.py format", + default=False, + action='store_true', + required=False) + parser.add_argument( + '-S', + help="Import search engines", + dest='search_query', + action='store_true', + default=False, + required=False) + parser.add_argument( + '-B', + help="Import plain bookmarks (no keywords)", + dest='bookmark_query', + action='store_true', + default=False, + required=False) + parser.add_argument( + '-K', + help="Import keywords (no search)", + dest='keyword_query', + action='store_true', + default=False, + required=False) parser.add_argument('bookmarks', help="Bookmarks file (html format)") args = parser.parse_args() return args @@ -100,16 +128,24 @@ def import_netscape_bookmarks(bookmarks_file, bookmark_types, output_format): with open(bookmarks_file, encoding='utf-8') as f: soup = bs4.BeautifulSoup(f, 'html.parser') bookmark_query = { - 'search': - lambda tag: (tag.name == 'a') and ('shortcuturl' in tag.attrs) and ('%s' in tag['href']), - 'keyword': - lambda tag: (tag.name == 'a') and ('shortcuturl' in tag.attrs) and ('%s' not in tag['href']), - 'bookmark': - lambda tag: (tag.name == 'a') and ('shortcuturl' not in tag.attrs) and (tag.string) + 'search': lambda tag: ( + (tag.name == 'a') and + ('shortcuturl' in tag.attrs) and + ('%s' in tag['href'])), + 'keyword': lambda tag: ( + (tag.name == 'a') and + ('shortcuturl' in tag.attrs) and + ('%s' not in tag['href'])), + 'bookmark': lambda tag: ( + (tag.name == 'a') and + ('shortcuturl' not in tag.attrs) and + (tag.string)), } output_template = { 'ncsearch': { - 'search': "c.url.searchengines['{tag[shortcuturl]}'] = '{tag[href]}' #{tag.string}" + 'search': + "c.url.searchengines['{tag[shortcuturl]}'] = " + "'{tag[href]}' #{tag.string}" }, 'search': { 'search': '{tag[shortcuturl]} = {tag[href]} #{tag.string}', @@ -127,10 +163,11 @@ def import_netscape_bookmarks(bookmarks_file, bookmark_types, output_format): for typ in bookmark_types: tags = soup.findAll(bookmark_query[typ]) for tag in tags: - if typ=='search': - tag['href'] = tag['href'].replace('%s','{}') + if typ == 'search': + tag['href'] = tag['href'].replace('%s', '{}') if tag['href'] not in bookmarks: - bookmarks.append(output_template[output_format][typ].format(tag=tag)) + bookmarks.append( + output_template[output_format][typ].format(tag=tag)) for bookmark in bookmarks: print(bookmark) From 799fe5deb3865f67570840ef07c61cc15ddec822 Mon Sep 17 00:00:00 2001 From: Ryan Farley Date: Fri, 22 Sep 2017 17:27:36 -0500 Subject: [PATCH 11/89] default to new search format --- scripts/importer.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/importer.py b/scripts/importer.py index aa53bccce..56480f762 100755 --- a/scripts/importer.py +++ b/scripts/importer.py @@ -32,8 +32,8 @@ def main(): output_format = '' if args.search_query or args.search_output: bookmark_types = ['search'] - if args.newconfig: - output_format = 'ncsearch' + if args.oldconfig: + output_format = 'oldsearch' else: output_format = 'search' else: @@ -88,8 +88,8 @@ def get_args(): default=False, required=False) parser.add_argument( - '--newconfig', - help="Output search engine format for new config.py format", + '--oldconfig', + help="Output search engine format for old qutebrowser.conf format", default=False, action='store_true', required=False) @@ -142,12 +142,12 @@ def import_netscape_bookmarks(bookmarks_file, bookmark_types, output_format): (tag.string)), } output_template = { - 'ncsearch': { + 'search': { 'search': "c.url.searchengines['{tag[shortcuturl]}'] = " "'{tag[href]}' #{tag.string}" }, - 'search': { + 'oldsearch': { 'search': '{tag[shortcuturl]} = {tag[href]} #{tag.string}', }, 'bookmark': { From aa0613c6d8c70ed9fa0de067e43e1cc5888bf9df Mon Sep 17 00:00:00 2001 From: Ryan Farley Date: Sun, 24 Sep 2017 06:25:13 -0500 Subject: [PATCH 12/89] support multiple input formats This restructures things to better support future implementations of other input formats. The default formats are specified in a global dict of browsers, which prevents duplicating the list of choices for browser in bother get_args() and main(), and a new option enables overriding of the default. --- scripts/importer.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/scripts/importer.py b/scripts/importer.py index 56480f762..edfd2e4cc 100755 --- a/scripts/importer.py +++ b/scripts/importer.py @@ -25,11 +25,20 @@ Currently only importing bookmarks from Netscape Bookmark files is supported. import argparse +browser_default_input_format = { + 'chromium': 'netscape', + 'ie': 'netscape', + 'firefox': 'netscape', + 'seamonkey': 'netscape', + 'palemoon': 'netscape' +} + def main(): args = get_args() bookmark_types = [] output_format = '' + input_format = args.input_format if args.search_query or args.search_output: bookmark_types = ['search'] if args.oldconfig: @@ -49,10 +58,11 @@ def main(): bookmark_types = ['bookmark', 'keyword'] if not output_format: output_format = 'quickmark' + if not input_format: + input_format = browser_default_input_format[args.browser] - if args.browser in ['chromium', 'firefox', 'ie']: - import_netscape_bookmarks(args.bookmarks, bookmark_types, - output_format) + import_function = {'netscape': import_netscape_bookmarks} + import_function[input_format](arg.bookmarks, bookmark_types, output_format) def get_args(): @@ -64,8 +74,14 @@ def get_args(): parser.add_argument( 'browser', help="Which browser? (chromium, firefox)", - choices=['chromium', 'firefox', 'ie'], + choices=browser_default_input_format.keys(), metavar='browser') + parser.add_argument( + '-i', + '--input-format', + help='Which input format? (overrides browser default)', + choices=set(browser_default_input_format.values()), + required=False) parser.add_argument( '-b', help="Output in bookmark format.", From d85a15f0a29c5264ae7c0504225c08ad5021519a Mon Sep 17 00:00:00 2001 From: Ryan Farley Date: Sun, 24 Sep 2017 06:41:12 -0500 Subject: [PATCH 13/89] style, variable name typo --- scripts/importer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/importer.py b/scripts/importer.py index edfd2e4cc..5e66a5202 100755 --- a/scripts/importer.py +++ b/scripts/importer.py @@ -62,7 +62,8 @@ def main(): input_format = browser_default_input_format[args.browser] import_function = {'netscape': import_netscape_bookmarks} - import_function[input_format](arg.bookmarks, bookmark_types, output_format) + import_function[input_format](args.bookmarks, bookmark_types, + output_format) def get_args(): From 84b2b05254db655deb11a63399d03b29512c2e03 Mon Sep 17 00:00:00 2001 From: Ryan Farley Date: Sun, 24 Sep 2017 12:02:44 -0500 Subject: [PATCH 14/89] help text mod Browser choices are now formatted in the help text rather than listed manually. Redundant line regarding output default removed from epilogue --- scripts/importer.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/scripts/importer.py b/scripts/importer.py index 5e66a5202..3a2c6a9f9 100755 --- a/scripts/importer.py +++ b/scripts/importer.py @@ -70,11 +70,10 @@ def get_args(): """Get the argparse parser.""" parser = argparse.ArgumentParser( epilog="To import bookmarks from Chromium, Firefox or IE, " - "export them to HTML in your browsers bookmark manager. " - "By default, this script will output in a quickmarks format.") + "export them to HTML in your browsers bookmark manager. ") parser.add_argument( 'browser', - help="Which browser? (chromium, firefox)", + help="Which browser? {%(choices)s}", choices=browser_default_input_format.keys(), metavar='browser') parser.add_argument( From a6ed079011b01315a2b3cce9c1be14dbd5b152c2 Mon Sep 17 00:00:00 2001 From: Ryan Farley Date: Mon, 25 Sep 2017 06:27:12 -0500 Subject: [PATCH 15/89] make browser argument optional --- scripts/importer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/importer.py b/scripts/importer.py index 3a2c6a9f9..c07fec0ec 100755 --- a/scripts/importer.py +++ b/scripts/importer.py @@ -75,6 +75,7 @@ def get_args(): 'browser', help="Which browser? {%(choices)s}", choices=browser_default_input_format.keys(), + nargs='?', metavar='browser') parser.add_argument( '-i', From 73c5666ff9c607ff7ee013a5d537318ea0a7f71b Mon Sep 17 00:00:00 2001 From: Ryan Farley Date: Tue, 10 Oct 2017 20:07:29 -0500 Subject: [PATCH 16/89] various importer fixes * Line breaks reinserted * None in place of '' * Check for browser before selecting default input format (to fix KeyError) * Remove redundant -S option and clarify help to make it slightly more obvious what output formats make sense * Added long-form arguments and slightly more sensible names (not really a fix, but I personally like having them) --- scripts/importer.py | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/scripts/importer.py b/scripts/importer.py index c07fec0ec..96bf1a942 100755 --- a/scripts/importer.py +++ b/scripts/importer.py @@ -18,13 +18,17 @@ # # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . + + """Tool to import data from other browsers. Currently only importing bookmarks from Netscape Bookmark files is supported. """ + import argparse + browser_default_input_format = { 'chromium': 'netscape', 'ie': 'netscape', @@ -37,9 +41,9 @@ browser_default_input_format = { def main(): args = get_args() bookmark_types = [] - output_format = '' + output_format = None input_format = args.input_format - if args.search_query or args.search_output: + if args.import_search: bookmark_types = ['search'] if args.oldconfig: output_format = 'oldsearch' @@ -50,15 +54,18 @@ def main(): output_format = 'bookmark' elif args.quickmark_output: output_format = 'quickmark' - if args.bookmark_query: + if args.import_bookmarks: bookmark_types.append('bookmark') - if args.keyword_query: + if args.import_keywords: bookmark_types.append('keyword') if not bookmark_types: bookmark_types = ['bookmark', 'keyword'] if not output_format: output_format = 'quickmark' if not input_format: + if not args.browser: + print("Must specify either browser or input format") + exit(1) input_format = browser_default_input_format[args.browser] import_function = {'netscape': import_netscape_bookmarks} @@ -85,22 +92,22 @@ def get_args(): required=False) parser.add_argument( '-b', + '--bookmark-output', help="Output in bookmark format.", - dest='bookmark_output', action='store_true', default=False, required=False) parser.add_argument( '-q', + '--quickmark-output', help="Output in quickmark format (default).", - dest='quickmark_output', action='store_true', default=False, required=False) parser.add_argument( '-s', - help="Output search engine format", - dest='search_output', + '--search-output', + help="Output config.py search engine format (negates -B and -K)", action='store_true', default=False, required=False) @@ -110,24 +117,17 @@ def get_args(): default=False, action='store_true', required=False) - parser.add_argument( - '-S', - help="Import search engines", - dest='search_query', - action='store_true', - default=False, - required=False) parser.add_argument( '-B', - help="Import plain bookmarks (no keywords)", - dest='bookmark_query', + '--import-bookmarks', + help="Import plain bookmarks (can be combiend with -K)", action='store_true', default=False, required=False) parser.add_argument( '-K', - help="Import keywords (no search)", - dest='keyword_query', + '--import-keywords', + help="Import keywords (can be combined with -B)", action='store_true', default=False, required=False) From af8a5c58da8dcac6ee8d6c821302b6092a07aaca Mon Sep 17 00:00:00 2001 From: Ryan Farley Date: Tue, 17 Oct 2017 14:47:45 -0500 Subject: [PATCH 17/89] use sys.exit --- scripts/importer.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/scripts/importer.py b/scripts/importer.py index 96bf1a942..d1d6fe9b5 100755 --- a/scripts/importer.py +++ b/scripts/importer.py @@ -27,7 +27,7 @@ Currently only importing bookmarks from Netscape Bookmark files is supported. import argparse - +import sys browser_default_input_format = { 'chromium': 'netscape', @@ -64,8 +64,7 @@ def main(): output_format = 'quickmark' if not input_format: if not args.browser: - print("Must specify either browser or input format") - exit(1) + sys.exit("Must specify either browser or input format") input_format = browser_default_input_format[args.browser] import_function = {'netscape': import_netscape_bookmarks} From b3445bc35a628fecefc51a6f490f705854df5c78 Mon Sep 17 00:00:00 2001 From: Luca Benci Date: Tue, 17 Oct 2017 22:08:54 +0200 Subject: [PATCH 18/89] Add default value for `caret_position` --- qutebrowser/misc/editor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/misc/editor.py b/qutebrowser/misc/editor.py index 54dd736d3..80df9b66f 100644 --- a/qutebrowser/misc/editor.py +++ b/qutebrowser/misc/editor.py @@ -96,7 +96,7 @@ class ExternalEditor(QObject): def on_proc_error(self, _err): self._cleanup() - def edit(self, text, caret_position): + def edit(self, text, caret_position=0): """Edit a given text. Args: From e508224a46dea915388668c18d0da15a1b0dff4e Mon Sep 17 00:00:00 2001 From: Luca Benci Date: Tue, 17 Oct 2017 22:35:01 +0200 Subject: [PATCH 19/89] Avoid the use of chained `replace`s --- qutebrowser/misc/editor.py | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/qutebrowser/misc/editor.py b/qutebrowser/misc/editor.py index 80df9b66f..69a83ffb5 100644 --- a/qutebrowser/misc/editor.py +++ b/qutebrowser/misc/editor.py @@ -174,20 +174,30 @@ class ExternalEditor(QObject): def _sub_placeholder(self, possible_placeholder, line, column): """Substitute a single placeholder. - The input to this function is not guaranteed to be a valid or known - placeholder. In this case the return value is the unchanged input. + If the `possible_placeholder` input to this function is valid it will + be substituted with the appropriate value, otherwise it will be left + unchanged. Args: possible_placeholder: an argument of editor.command. + line: the previously-calculated line number for the text caret + column: the previously-calculated column number for the text caret Return: The substituted placeholder or the original argument """ - sub = possible_placeholder\ - .replace('{}', self._filename)\ - .replace('{file}', self._filename)\ - .replace('{line}', str(line))\ - .replace('{line0}', str(line-1))\ - .replace('{column}', str(column))\ - .replace('{column0}', str(column-1)) - return sub + replacements = { + '{}': self._filename, + '{file}': self._filename, + '{line}': str(line), + '{line0}': str(line-1), + '{column}': str(column), + '{column0}': str(column-1) + } + + # The for loop would create a copy anyway, this should be more legible + ph = possible_placeholder + for old, new in replacements.items(): + ph = ph.replace(old, new) + + return ph From 233e72fef190dfd872843ac0d1efcc65dec0ee57 Mon Sep 17 00:00:00 2001 From: Luca Benci Date: Tue, 17 Oct 2017 22:38:11 +0200 Subject: [PATCH 20/89] Adjust docstring --- qutebrowser/misc/editor.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qutebrowser/misc/editor.py b/qutebrowser/misc/editor.py index 69a83ffb5..3c7522568 100644 --- a/qutebrowser/misc/editor.py +++ b/qutebrowser/misc/editor.py @@ -159,7 +159,8 @@ class ExternalEditor(QObject): """Start the editor with the file opened as self._filename. Args: - caret_position: The position of the caret in the text. + line: the line number to pass to the editor + column: the column number to pass to the editor """ self._proc = guiprocess.GUIProcess(what='editor', parent=self) self._proc.finished.connect(self.on_proc_closed) From f71053609256f543ca50092560ca4a48a84d1026 Mon Sep 17 00:00:00 2001 From: Luca Benci Date: Tue, 17 Oct 2017 22:48:43 +0200 Subject: [PATCH 21/89] Move line and column calculation to own function --- qutebrowser/misc/editor.py | 58 ++++++++++++++++++++++---------------- 1 file changed, 34 insertions(+), 24 deletions(-) diff --git a/qutebrowser/misc/editor.py b/qutebrowser/misc/editor.py index 3c7522568..d3d04ee59 100644 --- a/qutebrowser/misc/editor.py +++ b/qutebrowser/misc/editor.py @@ -123,30 +123,7 @@ class ExternalEditor(QObject): self._remove_file = True - # Here we calculate the line and column of the caret based on its - # position and the given text. - # - # NOTE: Both line and column are 1-based indexes, because that's what - # most editors use as line and column starting index. - # By "most" we mean at least vim, nvim, gvim, emacs, atom, sublimetext, - # notepad++, brackets, visual studio, QtCreator and so on. - # - # To find the line we just count how many newlines there are before - # the caret and add 1. - # - # To find the column we calculate the difference between the caret and - # the last newline before the caret. - # - # For example in the text `aaa\nbb|bbb` (| represents the caret): - # caret_position = 6 - # text[:caret_position] = `aaa\nbb` - # text[:caret_psotion].count('\n') = 1 - # caret_position - text[:caret_position].rfind('\n') = 3 - # - # Thus line, column = 2, 3, and the caret is indeed in the second - # line, third column - line = text[:caret_position].count('\n') + 1 - column = caret_position - text[:caret_position].rfind('\n') + line, column = self._calc_line_and_column(text, caret_position) self._start_editor(line=line, column=column) def edit_file(self, filename): @@ -172,6 +149,39 @@ class ExternalEditor(QObject): log.procs.debug("Calling \"{}\" with args {}".format(executable, args)) self._proc.start(executable, args) + def _calc_line_and_column(self, text, caret_position): + """Calculate line and column numbers given a text and caret position + + Args: + text: the text for which the numbers must be calculated + caret_position: the position of the caret in the text + + Return: + A (line, column) tuple of (int, int) + """ + # Both line and column are 1-based indexes, because that's what most + # editors use as line and column starting index. By "most" we mean at + # least vim, nvim, gvim, emacs, atom, sublimetext, notepad++, brackets, + # visual studio, QtCreator and so on. + # + # To find the line we just count how many newlines there are before the + # caret and add 1. + # + # To find the column we calculate the difference between the caret and + # the last newline before the caret. + # + # For example in the text `aaa\nbb|bbb` (| represents the caret): + # caret_position = 6 + # text[:caret_position] = `aaa\nbb` + # text[:caret_psotion].count('\n') = 1 + # caret_position - text[:caret_position].rfind('\n') = 3 + # + # Thus line, column = 2, 3, and the caret is indeed in the second + # line, third column + line = text[:caret_position].count('\n') + 1 + column = caret_position - text[:caret_position].rfind('\n') + return (line, column) + def _sub_placeholder(self, possible_placeholder, line, column): """Substitute a single placeholder. From 06b990c0d16ef840652843c9ad00304419637686 Mon Sep 17 00:00:00 2001 From: Luca Benci Date: Tue, 17 Oct 2017 23:03:42 +0200 Subject: [PATCH 22/89] Add ShellCommand tests for {file} --- tests/unit/config/test_configtypes.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/unit/config/test_configtypes.py b/tests/unit/config/test_configtypes.py index 9b5e23263..d3a058935 100644 --- a/tests/unit/config/test_configtypes.py +++ b/tests/unit/config/test_configtypes.py @@ -1818,6 +1818,9 @@ class TestShellCommand: ({'placeholder': '{}'}, '[foo, "{}", bar]', ['foo', '{}', 'bar']), ({'placeholder': '{}'}, '["foo{}bar"]', ['foo{}bar']), ({'placeholder': '{}'}, '[foo, "bar {}"]', ['foo', 'bar {}']), + ({'placeholder': '{file}'}, '[f, "{file}", b]', ['f', '{file}', 'b']), + ({'placeholder': '{file}'}, '["f{file}b"]', ['f{file}b']), + ({'placeholder': '{file}'}, '[f, "b {file}"]', ['f', 'b {file}']), ]) def test_valid(self, klass, kwargs, val, expected): cmd = klass(**kwargs) @@ -1827,6 +1830,8 @@ class TestShellCommand: @pytest.mark.parametrize('kwargs, val', [ ({'placeholder': '{}'}, '[foo, bar]'), ({'placeholder': '{}'}, '[foo, "{", "}", bar'), + ({'placeholder': '{file}'}, '[foo, bar]'), + ({'placeholder': '{file}'}, '[foo, "{fi", "le}", bar'), ]) def test_from_str_invalid(self, klass, kwargs, val): with pytest.raises(configexc.ValidationError): From 6f1b9b79848c85a2262c2f811ad5797a3e076dd2 Mon Sep 17 00:00:00 2001 From: Luca Benci Date: Tue, 17 Oct 2017 23:19:10 +0200 Subject: [PATCH 23/89] Add tests for line & column calculation --- tests/unit/misc/test_editor.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/unit/misc/test_editor.py b/tests/unit/misc/test_editor.py index d97a39f7b..75f0dd59c 100644 --- a/tests/unit/misc/test_editor.py +++ b/tests/unit/misc/test_editor.py @@ -157,6 +157,24 @@ class TestFileHandling: editor.edit("") +class TestLineColumnCalculation: + + """Test calculation for line and column given text and caret_position""" + + @pytest.mark.parametrize('text, caret_position, result', [ + ('', 0, (1, 1)), + ('a', 0, (1, 1)), + ('a\nb', 1, (1, 2)), + ('a\nb', 2, (2, 1)), + ('a\nb', 3, (2, 2)), + ('a\nbb\nccc', 4, (2, 3)), + ('a\nbb\nccc', 5, (3, 1)), + ('a\nbb\nccc', 8, (3, 4)), + ]) + def test_calculation(self, editor, text, caret_position, result): + assert editor._calc_line_and_column(text, caret_position) == result + + @pytest.mark.parametrize('initial_text, edited_text', [ ('', 'Hello'), ('Hello', 'World'), From addccd749236f1d43fb1cec25f64a986641cc2e2 Mon Sep 17 00:00:00 2001 From: Luca Benci Date: Wed, 18 Oct 2017 20:19:09 +0200 Subject: [PATCH 24/89] Move comment to docstring and fix typo --- qutebrowser/misc/editor.py | 39 +++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/qutebrowser/misc/editor.py b/qutebrowser/misc/editor.py index d3d04ee59..e4fc3bd35 100644 --- a/qutebrowser/misc/editor.py +++ b/qutebrowser/misc/editor.py @@ -152,6 +152,26 @@ class ExternalEditor(QObject): def _calc_line_and_column(self, text, caret_position): """Calculate line and column numbers given a text and caret position + Both line and column are 1-based indexes, because that's what most + editors use as line and column starting index. By "most" we mean at + least vim, nvim, gvim, emacs, atom, sublimetext, notepad++, brackets, + visual studio, QtCreator and so on. + + To find the line we just count how many newlines there are before the + caret and add 1. + + To find the column we calculate the difference between the caret and + the last newline before the caret. + + For example in the text `aaa\nbb|bbb` (| represents the caret): + caret_position = 6 + text[:caret_position] = `aaa\nbb` + text[:caret_position].count('\n') = 1 + caret_position - text[:caret_position].rfind('\n') = 3 + + Thus line, column = 2, 3, and the caret is indeed in the second + line, third column + Args: text: the text for which the numbers must be calculated caret_position: the position of the caret in the text @@ -159,25 +179,6 @@ class ExternalEditor(QObject): Return: A (line, column) tuple of (int, int) """ - # Both line and column are 1-based indexes, because that's what most - # editors use as line and column starting index. By "most" we mean at - # least vim, nvim, gvim, emacs, atom, sublimetext, notepad++, brackets, - # visual studio, QtCreator and so on. - # - # To find the line we just count how many newlines there are before the - # caret and add 1. - # - # To find the column we calculate the difference between the caret and - # the last newline before the caret. - # - # For example in the text `aaa\nbb|bbb` (| represents the caret): - # caret_position = 6 - # text[:caret_position] = `aaa\nbb` - # text[:caret_psotion].count('\n') = 1 - # caret_position - text[:caret_position].rfind('\n') = 3 - # - # Thus line, column = 2, 3, and the caret is indeed in the second - # line, third column line = text[:caret_position].count('\n') + 1 column = caret_position - text[:caret_position].rfind('\n') return (line, column) From 7907840ead0341d9afc2b9730ce99d8511de5160 Mon Sep 17 00:00:00 2001 From: Luca Benci Date: Wed, 18 Oct 2017 20:19:39 +0200 Subject: [PATCH 25/89] Add full stops --- qutebrowser/misc/editor.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qutebrowser/misc/editor.py b/qutebrowser/misc/editor.py index e4fc3bd35..85c552c8c 100644 --- a/qutebrowser/misc/editor.py +++ b/qutebrowser/misc/editor.py @@ -192,11 +192,11 @@ class ExternalEditor(QObject): Args: possible_placeholder: an argument of editor.command. - line: the previously-calculated line number for the text caret - column: the previously-calculated column number for the text caret + line: the previously-calculated line number for the text caret. + column: the previously-calculated column number for the text caret. Return: - The substituted placeholder or the original argument + The substituted placeholder or the original argument. """ replacements = { '{}': self._filename, From cf04219f790b2cde982774d728ddc85258fb4551 Mon Sep 17 00:00:00 2001 From: Luca Benci Date: Wed, 18 Oct 2017 20:20:05 +0200 Subject: [PATCH 26/89] Rename `possible_placeholder` to `arg` --- qutebrowser/misc/editor.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/qutebrowser/misc/editor.py b/qutebrowser/misc/editor.py index 85c552c8c..bbc4fe25e 100644 --- a/qutebrowser/misc/editor.py +++ b/qutebrowser/misc/editor.py @@ -183,15 +183,15 @@ class ExternalEditor(QObject): column = caret_position - text[:caret_position].rfind('\n') return (line, column) - def _sub_placeholder(self, possible_placeholder, line, column): + def _sub_placeholder(self, arg, line, column): """Substitute a single placeholder. - If the `possible_placeholder` input to this function is valid it will + If the `arg` input to this function is a valid placeholder it will be substituted with the appropriate value, otherwise it will be left unchanged. Args: - possible_placeholder: an argument of editor.command. + arg: an argument of editor.command. line: the previously-calculated line number for the text caret. column: the previously-calculated column number for the text caret. @@ -207,9 +207,7 @@ class ExternalEditor(QObject): '{column0}': str(column-1) } - # The for loop would create a copy anyway, this should be more legible - ph = possible_placeholder for old, new in replacements.items(): - ph = ph.replace(old, new) + arg = arg.replace(old, new) - return ph + return arg From 0d7a557396a74690cdfcf208c1fd7ed580d04161 Mon Sep 17 00:00:00 2001 From: Luca Benci Date: Wed, 18 Oct 2017 20:22:32 +0200 Subject: [PATCH 27/89] Fix configtypes tests so that placeholder is True --- tests/unit/config/test_configtypes.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/unit/config/test_configtypes.py b/tests/unit/config/test_configtypes.py index d3a058935..7b77ef8f3 100644 --- a/tests/unit/config/test_configtypes.py +++ b/tests/unit/config/test_configtypes.py @@ -1815,12 +1815,12 @@ class TestShellCommand: @pytest.mark.parametrize('kwargs, val, expected', [ ({}, '[foobar]', ['foobar']), - ({'placeholder': '{}'}, '[foo, "{}", bar]', ['foo', '{}', 'bar']), - ({'placeholder': '{}'}, '["foo{}bar"]', ['foo{}bar']), - ({'placeholder': '{}'}, '[foo, "bar {}"]', ['foo', 'bar {}']), - ({'placeholder': '{file}'}, '[f, "{file}", b]', ['f', '{file}', 'b']), - ({'placeholder': '{file}'}, '["f{file}b"]', ['f{file}b']), - ({'placeholder': '{file}'}, '[f, "b {file}"]', ['f', 'b {file}']), + ({'placeholder': True}, '[foo, "{}", bar]', ['foo', '{}', 'bar']), + ({'placeholder': True}, '["foo{}bar"]', ['foo{}bar']), + ({'placeholder': True}, '[foo, "bar {}"]', ['foo', 'bar {}']), + ({'placeholder': True}, '[f, "{file}", b]', ['f', '{file}', 'b']), + ({'placeholder': True}, '["f{file}b"]', ['f{file}b']), + ({'placeholder': True}, '[f, "b {file}"]', ['f', 'b {file}']), ]) def test_valid(self, klass, kwargs, val, expected): cmd = klass(**kwargs) @@ -1828,10 +1828,10 @@ class TestShellCommand: assert cmd.to_py(expected) == expected @pytest.mark.parametrize('kwargs, val', [ - ({'placeholder': '{}'}, '[foo, bar]'), - ({'placeholder': '{}'}, '[foo, "{", "}", bar'), - ({'placeholder': '{file}'}, '[foo, bar]'), - ({'placeholder': '{file}'}, '[foo, "{fi", "le}", bar'), + ({'placeholder': True}, '[foo, bar]'), + ({'placeholder': True}, '[foo, "{", "}", bar'), + ({'placeholder': True}, '[foo, bar]'), + ({'placeholder': True}, '[foo, "{fi", "le}", bar'), ]) def test_from_str_invalid(self, klass, kwargs, val): with pytest.raises(configexc.ValidationError): From 937d0d068847dfcafe41f18c593a35c95767da0a Mon Sep 17 00:00:00 2001 From: Luca Benci Date: Wed, 18 Oct 2017 20:30:16 +0200 Subject: [PATCH 28/89] Add some more tests --- tests/unit/config/test_configtypes.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/unit/config/test_configtypes.py b/tests/unit/config/test_configtypes.py index 7b77ef8f3..36988a0a7 100644 --- a/tests/unit/config/test_configtypes.py +++ b/tests/unit/config/test_configtypes.py @@ -1832,6 +1832,11 @@ class TestShellCommand: ({'placeholder': True}, '[foo, "{", "}", bar'), ({'placeholder': True}, '[foo, bar]'), ({'placeholder': True}, '[foo, "{fi", "le}", bar'), + + # Like valid ones but with wrong placeholder + ({'placeholder': True}, '[f, "{wrong}", b]'), + ({'placeholder': True}, '["f{wrong}b"]'), + ({'placeholder': True}, '[f, "b {wrong}"]'), ]) def test_from_str_invalid(self, klass, kwargs, val): with pytest.raises(configexc.ValidationError): From 9b177ae8e7fe8927088ac7cc8d1ed20f63f8d21f Mon Sep 17 00:00:00 2001 From: Luca Benci Date: Wed, 18 Oct 2017 20:33:14 +0200 Subject: [PATCH 29/89] Remove single-function test class (move test out) --- tests/unit/misc/test_editor.py | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/tests/unit/misc/test_editor.py b/tests/unit/misc/test_editor.py index 75f0dd59c..62abe7461 100644 --- a/tests/unit/misc/test_editor.py +++ b/tests/unit/misc/test_editor.py @@ -157,24 +157,6 @@ class TestFileHandling: editor.edit("") -class TestLineColumnCalculation: - - """Test calculation for line and column given text and caret_position""" - - @pytest.mark.parametrize('text, caret_position, result', [ - ('', 0, (1, 1)), - ('a', 0, (1, 1)), - ('a\nb', 1, (1, 2)), - ('a\nb', 2, (2, 1)), - ('a\nb', 3, (2, 2)), - ('a\nbb\nccc', 4, (2, 3)), - ('a\nbb\nccc', 5, (3, 1)), - ('a\nbb\nccc', 8, (3, 4)), - ]) - def test_calculation(self, editor, text, caret_position, result): - assert editor._calc_line_and_column(text, caret_position) == result - - @pytest.mark.parametrize('initial_text, edited_text', [ ('', 'Hello'), ('Hello', 'World'), @@ -195,3 +177,18 @@ def test_modify(qtbot, editor, initial_text, edited_text): editor._proc.finished.emit(0, QProcess.NormalExit) assert blocker.args == [edited_text] + + +@pytest.mark.parametrize('text, caret_position, result', [ + ('', 0, (1, 1)), + ('a', 0, (1, 1)), + ('a\nb', 1, (1, 2)), + ('a\nb', 2, (2, 1)), + ('a\nb', 3, (2, 2)), + ('a\nbb\nccc', 4, (2, 3)), + ('a\nbb\nccc', 5, (3, 1)), + ('a\nbb\nccc', 8, (3, 4)), +]) +def test_calculation(editor, text, caret_position, result): + """Test calculation for line and column given text and caret_position""" + assert editor._calc_line_and_column(text, caret_position) == result From 04365262036ef40cca9f4a5ab7e7d7d832a15258 Mon Sep 17 00:00:00 2001 From: Luca Benci Date: Wed, 18 Oct 2017 21:08:22 +0200 Subject: [PATCH 30/89] Change default editor command --- qutebrowser/config/configdata.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index 9f47a7d70..1d6dd601a 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -746,11 +746,11 @@ editor.command: type: name: ShellCommand placeholder: true - default: ["gvim", "-f", "{}"] + default: ["gvim", "-f", "{file}", "-c", "normal {line}G{column0}l"] desc: >- The editor (and arguments) to use for the `open-editor` command. - `{}` gets replaced by the filename of the file to be edited. + `{file}` gets replaced by the filename of the file to be edited. editor.encoding: type: Encoding From 9613deb89da6462300dcd1e245aabb2d5ebe0a34 Mon Sep 17 00:00:00 2001 From: Luca Benci Date: Wed, 18 Oct 2017 21:20:05 +0200 Subject: [PATCH 31/89] Document new `editor.command` placeholders --- doc/help/settings.asciidoc | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index d5511dff0..31aff3740 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -1913,7 +1913,14 @@ Default: +pass:[-1]+ [[editor.command]] === editor.command The editor (and arguments) to use for the `open-editor` command. -`{}` gets replaced by the filename of the file to be edited. +Several placeholders are supported. Placeholders are substituted by the +respective value when executing the command. + +`{file}` gets replaced by the filename of the file to be edited. +`{line}` gets replaced by the line in which the caret is found in the text. +`{column}` gets replaced by the column in which the caret is found in the text. +`{line0}` same as `{line}`, but starting from index 0 +`{column0}` same as `{column}`, but starting from index 0 Type: <> From e705ea7e568535528c8cf8200684214bf9364687 Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Fri, 20 Oct 2017 15:40:11 -0400 Subject: [PATCH 32/89] Rename _minimum_tab_size_hint_helper --- qutebrowser/mainwindow/tabwidget.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py index 632486200..edabf0be0 100644 --- a/qutebrowser/mainwindow/tabwidget.py +++ b/qutebrowser/mainwindow/tabwidget.py @@ -447,13 +447,14 @@ class TabBar(QTabBar): Return: A QSize of the smallest tab size we can make. """ - return self.__minimumTabSizeHintHelper(self.tabText(index), - self.tabIcon(index), ellipsis) + return self._minimum_tab_size_hint_helper(self.tabText(index), + self.tabIcon(index), + ellipsis) @functools.lru_cache(maxsize=100) - def __minimumTabSizeHintHelper(self, tab_text: str, - icon, - ellipsis: bool) -> QSize: + def _minimum_tab_size_hint_helper(self, tab_text: str, + icon, + ellipsis: bool) -> QSize: """Helper function to cache tab results.""" text = '\u2026' if ellipsis else tab_text # Don't ever shorten if text is shorter than the ellipsis From fde4495bc795699aa413892fe30b5390b8592f99 Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Fri, 20 Oct 2017 16:35:11 -0400 Subject: [PATCH 33/89] Clear cache on config changes --- qutebrowser/mainwindow/tabwidget.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py index edabf0be0..10cdd4119 100644 --- a/qutebrowser/mainwindow/tabwidget.py +++ b/qutebrowser/mainwindow/tabwidget.py @@ -323,7 +323,7 @@ class TabBar(QTabBar): return self.parent().currentWidget() @pyqtSlot(str) - def _on_config_changed(self, option): + def _on_config_changed(self, option: str): if option == 'fonts.tabs': self._set_font() elif option == 'tabs.favicons.scale': @@ -338,6 +338,12 @@ class TabBar(QTabBar): if option.startswith('colors.tabs.'): self.update() + # Clear _minimum_tab_size_hint_helper cache when appropriate + if option in ["tabs.indicator_padding", + "tabs.padding", + "tabs.width.indicator"]: + self._minimum_tab_size_hint_helper.cache_clear() + def _on_show_switching_delay_changed(self): """Set timer interval when tabs.show_switching_delay got changed.""" self._auto_hide_timer.setInterval(config.val.tabs.show_switching_delay) @@ -455,7 +461,12 @@ class TabBar(QTabBar): def _minimum_tab_size_hint_helper(self, tab_text: str, icon, ellipsis: bool) -> QSize: - """Helper function to cache tab results.""" + """Helper function to cache tab results. + + Acessing config values in here should be added to _on_config_changed to + ensure cache is flushed when needed. + """ + print("running!") text = '\u2026' if ellipsis else tab_text # Don't ever shorten if text is shorter than the ellipsis text_width = min(self.fontMetrics().width(text), From caae1c70085d61413c760af0861a3f79e840179d Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Fri, 20 Oct 2017 21:50:35 -0400 Subject: [PATCH 34/89] Fix blowing cache for different icons --- qutebrowser/mainwindow/tabwidget.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py index 10cdd4119..b8b78beea 100644 --- a/qutebrowser/mainwindow/tabwidget.py +++ b/qutebrowser/mainwindow/tabwidget.py @@ -453,20 +453,25 @@ class TabBar(QTabBar): Return: A QSize of the smallest tab size we can make. """ + icon = self.tabIcon(index) + extent = self.style().pixelMetric(QStyle.PM_TabBarIconSize, None, self) + if not icon: + icon_width = 0 + else: + icon_width = icon.actualSize(QSize(extent, extent)).width() return self._minimum_tab_size_hint_helper(self.tabText(index), - self.tabIcon(index), + icon_width, ellipsis) - @functools.lru_cache(maxsize=100) + @functools.lru_cache(maxsize=2**10) def _minimum_tab_size_hint_helper(self, tab_text: str, - icon, + icon_width: int, ellipsis: bool) -> QSize: """Helper function to cache tab results. Acessing config values in here should be added to _on_config_changed to ensure cache is flushed when needed. """ - print("running!") text = '\u2026' if ellipsis else tab_text # Don't ever shorten if text is shorter than the ellipsis text_width = min(self.fontMetrics().width(text), @@ -476,14 +481,8 @@ class TabBar(QTabBar): padding_h = padding.left + padding.right padding_h += indicator_padding.left + indicator_padding.right padding_v = padding.top + padding.bottom - if icon.isNull(): - icon_size = QSize(0, 0) - else: - extent = self.style().pixelMetric(QStyle.PM_TabBarIconSize, None, - self) - icon_size = icon.actualSize(QSize(extent, extent)) height = self.fontMetrics().height() + padding_v - width = (text_width + icon_size.width() + + width = (text_width + icon_width + padding_h + config.val.tabs.width.indicator) return QSize(width, height) From b49947459918269189a8d633923dd9a9cd612567 Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Sat, 21 Oct 2017 00:31:34 -0400 Subject: [PATCH 35/89] Prevent calling _tab_pinned on every tab twice --- qutebrowser/mainwindow/tabwidget.py | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py index b8b78beea..4f2e33034 100644 --- a/qutebrowser/mainwindow/tabwidget.py +++ b/qutebrowser/mainwindow/tabwidget.py @@ -486,17 +486,15 @@ class TabBar(QTabBar): padding_h + config.val.tabs.width.indicator) return QSize(width, height) - def _tab_total_width_pinned(self): - """Get the current total width of pinned tabs. - - This width is calculated assuming no shortening due to ellipsis.""" - return sum(self.minimumTabSizeHint(idx, ellipsis=False).width() - for idx in range(self.count()) - if self._tab_pinned(idx)) - - def _pinnedCount(self) -> int: - """Get the number of pinned tabs.""" - return sum(self._tab_pinned(idx) for idx in range(self.count())) + def _pinned_statistics(self) -> (int, int): + """Get the number of pinned tabs and the total width of pinned tabs.""" + pinned_list = [idx + for idx in range(self.count()) + if self._tab_pinned(idx)] + pinned_count = len(pinned_list) + pinned_width = sum(self.minimumTabSizeHint(idx, ellipsis=False).width() + for idx in pinned_list) + return (pinned_count, pinned_width) def _tab_pinned(self, index: int) -> bool: """Return True if tab is pinned.""" @@ -535,8 +533,8 @@ class TabBar(QTabBar): return QSize() else: pinned = self._tab_pinned(index) - no_pinned_count = self.count() - self._pinnedCount() - pinned_width = self._tab_total_width_pinned() + pinned_count, pinned_width = self._pinned_statistics() + no_pinned_count = self.count() - pinned_count no_pinned_width = self.width() - pinned_width if pinned: From 49daa7aab88fca09034eae4dae36176587641125 Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Sat, 21 Oct 2017 16:18:23 -0400 Subject: [PATCH 36/89] Add most recent tab bar to cache statistics --- qutebrowser/misc/utilcmds.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/qutebrowser/misc/utilcmds.py b/qutebrowser/misc/utilcmds.py index bf1e94586..e4c4fc033 100644 --- a/qutebrowser/misc/utilcmds.py +++ b/qutebrowser/misc/utilcmds.py @@ -181,9 +181,15 @@ def debug_cache_stats(): except ImportError: pass + tabbed_browser = objreg.get('tabbed-browser', scope='window', + window='last-focused') + tabbed_browser_info = tabbed_browser.tabBar(). \ + _minimum_tab_size_hint_helper.cache_info() + log.misc.debug('is_valid_prefix: {}'.format(prefix_info)) log.misc.debug('_render_stylesheet: {}'.format(render_stylesheet_info)) log.misc.debug('history: {}'.format(history_info)) + log.misc.debug('tab width cache: {}'.format(tabbed_browser_info)) @cmdutils.register(debug=True) From 31f1025ff858e1222d1335f668759173cc961b35 Mon Sep 17 00:00:00 2001 From: Ryan Farley Date: Sat, 21 Oct 2017 18:12:25 -0500 Subject: [PATCH 37/89] escape search engine URLs in importer --- scripts/importer.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/scripts/importer.py b/scripts/importer.py index d1d6fe9b5..b3ab90fc2 100755 --- a/scripts/importer.py +++ b/scripts/importer.py @@ -135,6 +135,14 @@ def get_args(): return args +def search_escape(url): + """Escapes URLs such that preexisting { and } are handled properly. + + Will obviously trash a properly-formatted Qutebrowser URL. + """ + return url.replace('{', '{{').replace('}', '}}') + + def import_netscape_bookmarks(bookmarks_file, bookmark_types, output_format): """Import bookmarks from a NETSCAPE-Bookmark-file v1. @@ -180,7 +188,7 @@ def import_netscape_bookmarks(bookmarks_file, bookmark_types, output_format): tags = soup.findAll(bookmark_query[typ]) for tag in tags: if typ == 'search': - tag['href'] = tag['href'].replace('%s', '{}') + tag['href'] = search_escape(tag['href']).replace('%s', '{}') if tag['href'] not in bookmarks: bookmarks.append( output_template[output_format][typ].format(tag=tag)) From 8b91a74aefd3799445ec82f99f9cfe4dc406c94a Mon Sep 17 00:00:00 2001 From: Luca Benci Date: Sun, 22 Oct 2017 20:02:06 +0200 Subject: [PATCH 38/89] Fix broken test after default config change --- tests/unit/config/test_configcommands.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/unit/config/test_configcommands.py b/tests/unit/config/test_configcommands.py index 1841e6260..a44327bd2 100644 --- a/tests/unit/config/test_configcommands.py +++ b/tests/unit/config/test_configcommands.py @@ -60,8 +60,9 @@ class TestSet: @pytest.mark.parametrize('option, old_value, inp, new_value', [ ('url.auto_search', 'naive', 'dns', 'dns'), # https://github.com/qutebrowser/qutebrowser/issues/2962 - ('editor.command', ['gvim', '-f', '{}'], '[emacs, "{}"]', - ['emacs', '{}']), + ('editor.command', + ['gvim', '-f', '{file}', '-c', 'normal {line}G{column0}l'], + '[emacs, "{}"]', ['emacs', '{}']), ]) def test_set_simple(self, monkeypatch, commands, config_stub, temp, option, old_value, inp, new_value): From 96bbdb19e6e678924c412ff530b1b715c73c97ea Mon Sep 17 00:00:00 2001 From: Luca Benci Date: Sun, 22 Oct 2017 20:02:32 +0200 Subject: [PATCH 39/89] Add missing docstrings --- qutebrowser/browser/webengine/webengineelem.py | 1 + qutebrowser/browser/webkit/webkitelem.py | 1 + 2 files changed, 2 insertions(+) diff --git a/qutebrowser/browser/webengine/webengineelem.py b/qutebrowser/browser/webengine/webengineelem.py index 522605bba..fe8541e77 100644 --- a/qutebrowser/browser/webengine/webengineelem.py +++ b/qutebrowser/browser/webengine/webengineelem.py @@ -134,6 +134,7 @@ class WebEngineElement(webelem.AbstractWebElement): self._js_call('set_value', value) def caret_position(self): + """Get the text caret position for the current element""" return self._js_dict.get('caret_position', 0) def insert_text(self, text): diff --git a/qutebrowser/browser/webkit/webkitelem.py b/qutebrowser/browser/webkit/webkitelem.py index e130e4623..4ddaf5e51 100644 --- a/qutebrowser/browser/webkit/webkitelem.py +++ b/qutebrowser/browser/webkit/webkitelem.py @@ -127,6 +127,7 @@ class WebKitElement(webelem.AbstractWebElement): self._elem.evaluateJavaScript("this.value='{}'".format(value)) def caret_position(self): + """Get the text caret position for the current element""" self._check_vanished() pos = self._elem.evaluateJavaScript('this.selectionStart') assert isinstance(pos, (int, float, type(None))) From 1f521da134ad1e3f4888379c3711f96d83773e20 Mon Sep 17 00:00:00 2001 From: Luca Benci Date: Sun, 22 Oct 2017 20:03:46 +0200 Subject: [PATCH 40/89] Add missing full-stops --- doc/help/settings.asciidoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index 31aff3740..991596f3d 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -1919,8 +1919,8 @@ respective value when executing the command. `{file}` gets replaced by the filename of the file to be edited. `{line}` gets replaced by the line in which the caret is found in the text. `{column}` gets replaced by the column in which the caret is found in the text. -`{line0}` same as `{line}`, but starting from index 0 -`{column0}` same as `{column}`, but starting from index 0 +`{line0}` same as `{line}`, but starting from index 0. +`{column0}` same as `{column}`, but starting from index 0. Type: <> From cb6f4313d7e74d1cf8ab7152958b617fad34c882 Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Tue, 24 Oct 2017 10:18:10 -0400 Subject: [PATCH 41/89] Lower tabbar cache bound and clean up code --- qutebrowser/mainwindow/tabwidget.py | 4 ++-- qutebrowser/misc/utilcmds.py | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py index 4f2e33034..67d138fd4 100644 --- a/qutebrowser/mainwindow/tabwidget.py +++ b/qutebrowser/mainwindow/tabwidget.py @@ -463,13 +463,13 @@ class TabBar(QTabBar): icon_width, ellipsis) - @functools.lru_cache(maxsize=2**10) + @functools.lru_cache(maxsize=2**9) def _minimum_tab_size_hint_helper(self, tab_text: str, icon_width: int, ellipsis: bool) -> QSize: """Helper function to cache tab results. - Acessing config values in here should be added to _on_config_changed to + Config values accessed in here should be added to _on_config_changed to ensure cache is flushed when needed. """ text = '\u2026' if ellipsis else tab_text diff --git a/qutebrowser/misc/utilcmds.py b/qutebrowser/misc/utilcmds.py index e4c4fc033..07949b771 100644 --- a/qutebrowser/misc/utilcmds.py +++ b/qutebrowser/misc/utilcmds.py @@ -171,6 +171,7 @@ def debug_cache_stats(): prefix_info = configdata.is_valid_prefix.cache_info() # pylint: disable=protected-access render_stylesheet_info = config._render_stylesheet.cache_info() + # pylint: enable=protected-access history_info = None try: @@ -183,8 +184,10 @@ def debug_cache_stats(): tabbed_browser = objreg.get('tabbed-browser', scope='window', window='last-focused') + # pylint: disable=protected-access tabbed_browser_info = tabbed_browser.tabBar(). \ _minimum_tab_size_hint_helper.cache_info() + # pylint: enable=protected-access log.misc.debug('is_valid_prefix: {}'.format(prefix_info)) log.misc.debug('_render_stylesheet: {}'.format(render_stylesheet_info)) From 4ed7fe731d33323d93690aaeb254f0ae1f8d8aa6 Mon Sep 17 00:00:00 2001 From: Ryan Farley Date: Tue, 24 Oct 2017 17:31:42 -0500 Subject: [PATCH 42/89] removed wrong option --- scripts/importer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/importer.py b/scripts/importer.py index b3ab90fc2..d239c62c5 100755 --- a/scripts/importer.py +++ b/scripts/importer.py @@ -43,7 +43,7 @@ def main(): bookmark_types = [] output_format = None input_format = args.input_format - if args.import_search: + if args.search_output: bookmark_types = ['search'] if args.oldconfig: output_format = 'oldsearch' From 137a7114e117a92e4e9283c786f21b0c6c0c59ef Mon Sep 17 00:00:00 2001 From: Ryan Farley Date: Tue, 24 Oct 2017 19:41:22 -0500 Subject: [PATCH 43/89] importer: documentation of bookmark types --- scripts/importer.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/importer.py b/scripts/importer.py index d239c62c5..f5da1e47e 100755 --- a/scripts/importer.py +++ b/scripts/importer.py @@ -146,7 +146,11 @@ def search_escape(url): def import_netscape_bookmarks(bookmarks_file, bookmark_types, output_format): """Import bookmarks from a NETSCAPE-Bookmark-file v1. - Generated by Chromium, Firefox, IE and possibly more browsers + Generated by Chromium, Firefox, IE and possibly more browsers. Not all + export all possible bookmark types: + - Firefox mostly works with everything + - Chrome doesn't support keywords at all; searches are a separate + database """ import bs4 with open(bookmarks_file, encoding='utf-8') as f: From 38e3c1ee8fcb65750c008dd4b73eb9634ce2d99d Mon Sep 17 00:00:00 2001 From: Ryan Farley Date: Tue, 24 Oct 2017 19:58:38 -0500 Subject: [PATCH 44/89] fix whitespace --- scripts/importer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/importer.py b/scripts/importer.py index f5da1e47e..5886efa5a 100755 --- a/scripts/importer.py +++ b/scripts/importer.py @@ -146,10 +146,10 @@ def search_escape(url): def import_netscape_bookmarks(bookmarks_file, bookmark_types, output_format): """Import bookmarks from a NETSCAPE-Bookmark-file v1. - Generated by Chromium, Firefox, IE and possibly more browsers. Not all + Generated by Chromium, Firefox, IE and possibly more browsers. Not all export all possible bookmark types: - Firefox mostly works with everything - - Chrome doesn't support keywords at all; searches are a separate + - Chrome doesn't support keywords at all; searches are a separate database """ import bs4 From f195b7e4d24851035a7c1cf127cd4f52195b4f9f Mon Sep 17 00:00:00 2001 From: Luca Benci Date: Wed, 25 Oct 2017 21:18:53 +0200 Subject: [PATCH 45/89] Fix flake8 failures --- qutebrowser/browser/webengine/webengineelem.py | 2 +- qutebrowser/browser/webkit/webkitelem.py | 2 +- qutebrowser/misc/editor.py | 2 +- tests/unit/misc/test_editor.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/qutebrowser/browser/webengine/webengineelem.py b/qutebrowser/browser/webengine/webengineelem.py index fe8541e77..fb528a93d 100644 --- a/qutebrowser/browser/webengine/webengineelem.py +++ b/qutebrowser/browser/webengine/webengineelem.py @@ -134,7 +134,7 @@ class WebEngineElement(webelem.AbstractWebElement): self._js_call('set_value', value) def caret_position(self): - """Get the text caret position for the current element""" + """Get the text caret position for the current element.""" return self._js_dict.get('caret_position', 0) def insert_text(self, text): diff --git a/qutebrowser/browser/webkit/webkitelem.py b/qutebrowser/browser/webkit/webkitelem.py index 4ddaf5e51..86c701234 100644 --- a/qutebrowser/browser/webkit/webkitelem.py +++ b/qutebrowser/browser/webkit/webkitelem.py @@ -127,7 +127,7 @@ class WebKitElement(webelem.AbstractWebElement): self._elem.evaluateJavaScript("this.value='{}'".format(value)) def caret_position(self): - """Get the text caret position for the current element""" + """Get the text caret position for the current element.""" self._check_vanished() pos = self._elem.evaluateJavaScript('this.selectionStart') assert isinstance(pos, (int, float, type(None))) diff --git a/qutebrowser/misc/editor.py b/qutebrowser/misc/editor.py index bbc4fe25e..2fd1d8597 100644 --- a/qutebrowser/misc/editor.py +++ b/qutebrowser/misc/editor.py @@ -150,7 +150,7 @@ class ExternalEditor(QObject): self._proc.start(executable, args) def _calc_line_and_column(self, text, caret_position): - """Calculate line and column numbers given a text and caret position + r"""Calculate line and column numbers given a text and caret position. Both line and column are 1-based indexes, because that's what most editors use as line and column starting index. By "most" we mean at diff --git a/tests/unit/misc/test_editor.py b/tests/unit/misc/test_editor.py index 62abe7461..683b38841 100644 --- a/tests/unit/misc/test_editor.py +++ b/tests/unit/misc/test_editor.py @@ -190,5 +190,5 @@ def test_modify(qtbot, editor, initial_text, edited_text): ('a\nbb\nccc', 8, (3, 4)), ]) def test_calculation(editor, text, caret_position, result): - """Test calculation for line and column given text and caret_position""" + """Test calculation for line and column given text and caret_position.""" assert editor._calc_line_and_column(text, caret_position) == result From 3d87f4ebdf0050fc1184c9ef46f5deeeeb359fe9 Mon Sep 17 00:00:00 2001 From: Ryan Farley Date: Wed, 25 Oct 2017 14:52:53 -0500 Subject: [PATCH 46/89] default to Netscape format for importer --- scripts/importer.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/scripts/importer.py b/scripts/importer.py index 5886efa5a..ca3eaa206 100755 --- a/scripts/importer.py +++ b/scripts/importer.py @@ -63,9 +63,11 @@ def main(): if not output_format: output_format = 'quickmark' if not input_format: - if not args.browser: - sys.exit("Must specify either browser or input format") - input_format = browser_default_input_format[args.browser] + if args.browser: + input_format = browser_default_input_format[args.browser] + else: + #default to netscape + input_format = 'netscape' import_function = {'netscape': import_netscape_bookmarks} import_function[input_format](args.bookmarks, bookmark_types, @@ -86,7 +88,8 @@ def get_args(): parser.add_argument( '-i', '--input-format', - help='Which input format? (overrides browser default)', + help='Which input format? (overrides browser default; "netscape" if ' + 'neither given)', choices=set(browser_default_input_format.values()), required=False) parser.add_argument( From 3fd7fb3e14854306a801c9caa9bb2a89be150718 Mon Sep 17 00:00:00 2001 From: Luca Benci Date: Wed, 25 Oct 2017 22:38:44 +0200 Subject: [PATCH 47/89] Do not assume elem.selectionStart exists --- qutebrowser/javascript/webelem.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/qutebrowser/javascript/webelem.js b/qutebrowser/javascript/webelem.js index a37fcd6c8..7f17a1209 100644 --- a/qutebrowser/javascript/webelem.js +++ b/qutebrowser/javascript/webelem.js @@ -48,12 +48,20 @@ window._qutebrowser.webelem = (function() { var id = elements.length; elements[id] = elem; + // InvalidStateError will be thrown if elem doesn't have selectionStart + var caret_position = 0; + try { + caret_position = elem.selectionStart; + } catch (e) { + // nothing to do, caret_position is already 0 + } + var out = { "id": id, "value": elem.value, "outer_xml": elem.outerHTML, "rects": [], // Gets filled up later - "caret_position": elem.selectionStart, + "caret_position": caret_position, }; // https://github.com/qutebrowser/qutebrowser/issues/2569 From ae2dad7d188ed1c7f43bf99bd99166a56b0e2f42 Mon Sep 17 00:00:00 2001 From: Luca Benci Date: Wed, 25 Oct 2017 22:43:17 +0200 Subject: [PATCH 48/89] Only catch the correct exception --- qutebrowser/javascript/webelem.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/qutebrowser/javascript/webelem.js b/qutebrowser/javascript/webelem.js index 7f17a1209..06f56bf1b 100644 --- a/qutebrowser/javascript/webelem.js +++ b/qutebrowser/javascript/webelem.js @@ -53,7 +53,9 @@ window._qutebrowser.webelem = (function() { try { caret_position = elem.selectionStart; } catch (e) { - // nothing to do, caret_position is already 0 + if (e instanceof DOMException && e.name === "InvalidStateError") { + // nothing to do, caret_position is already 0 + } } var out = { From ff7edf79e7ec5bbb6923420b99a7e90d527115c8 Mon Sep 17 00:00:00 2001 From: Luca Benci Date: Wed, 25 Oct 2017 22:53:54 +0200 Subject: [PATCH 49/89] Rethrow exception if we can't handle it --- qutebrowser/javascript/webelem.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/qutebrowser/javascript/webelem.js b/qutebrowser/javascript/webelem.js index 06f56bf1b..283f772b5 100644 --- a/qutebrowser/javascript/webelem.js +++ b/qutebrowser/javascript/webelem.js @@ -55,6 +55,9 @@ window._qutebrowser.webelem = (function() { } catch (e) { if (e instanceof DOMException && e.name === "InvalidStateError") { // nothing to do, caret_position is already 0 + } else { + // not the droid we're looking for + throw e } } From 5d2975293b08fbb71f919783b1c5e830f083105b Mon Sep 17 00:00:00 2001 From: Ryan Farley Date: Wed, 25 Oct 2017 16:49:12 -0500 Subject: [PATCH 50/89] remove unused import --- scripts/importer.py | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/importer.py b/scripts/importer.py index ca3eaa206..436971e58 100755 --- a/scripts/importer.py +++ b/scripts/importer.py @@ -27,7 +27,6 @@ Currently only importing bookmarks from Netscape Bookmark files is supported. import argparse -import sys browser_default_input_format = { 'chromium': 'netscape', From 6519f500a9f779ce868d1dd9556902e607cde6ab Mon Sep 17 00:00:00 2001 From: Ryan Roden-Corrent Date: Wed, 25 Oct 2017 10:24:38 -0400 Subject: [PATCH 51/89] Add --private flag to edit_url. The command :edit-url --private (or :edit-url -p) will spawn a new private window with the url input from the editor. I had to add 'Given I have a fresh instance' to the feature file to ensure tests were not interfering. Resolves #3185. --- qutebrowser/browser/commands.py | 14 +++++++++----- tests/end2end/features/editor.feature | 23 +++++++++++++++++++++++ 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 8f5019499..6c8394771 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -2112,7 +2112,8 @@ class CommandDispatcher: self._current_widget().clear_ssl_errors() @cmdutils.register(instance='command-dispatcher', scope='window') - def edit_url(self, url=None, bg=False, tab=False, window=False): + def edit_url(self, url=None, bg=False, tab=False, window=False, + private=False): """Navigate to a url formed in an external editor. The editor which should be launched can be configured via the @@ -2123,6 +2124,7 @@ class CommandDispatcher: bg: Open in a new background tab. tab: Open in a new tab. window: Open in a new window. + private: Open a new window in private browsing mode. """ cmdutils.check_exclusive((tab, bg, window), 'tbw') @@ -2133,7 +2135,7 @@ class CommandDispatcher: # Passthrough for openurl args (e.g. -t, -b, -w) ed.editing_finished.connect(functools.partial( self._open_if_changed, old_url=old_url, bg=bg, tab=tab, - window=window)) + window=window, private=private)) ed.edit(url or old_url) @@ -2158,7 +2160,7 @@ class CommandDispatcher: self._tabbed_browser.jump_mark(key) def _open_if_changed(self, url=None, old_url=None, bg=False, tab=False, - window=False): + window=False, private=False): """Open a URL unless it's already open in the tab. Args: @@ -2167,9 +2169,11 @@ class CommandDispatcher: bg: Open in a new background tab. tab: Open in a new tab. window: Open in a new window. + private: Open a new window in private browsing mode. """ - if bg or tab or window or url != old_url: - self.openurl(url=url, bg=bg, tab=tab, window=window) + if bg or tab or window or private or url != old_url: + self.openurl(url=url, bg=bg, tab=tab, window=window, + private=private) @cmdutils.register(instance='command-dispatcher', scope='window') def fullscreen(self, leave=False): diff --git a/tests/end2end/features/editor.feature b/tests/end2end/features/editor.feature index 6c2f1144d..dbbc06879 100644 --- a/tests/end2end/features/editor.feature +++ b/tests/end2end/features/editor.feature @@ -2,6 +2,9 @@ Feature: Opening external editors + Background: + Given I have a fresh instance + ## :edit-url Scenario: Editing a URL @@ -49,6 +52,26 @@ Feature: Opening external editors - active: true url: http://localhost:*/data/numbers/2.txt + Scenario: Editing a URL with -p + When I open data/numbers/1.txt in a new tab + And I run :tab-only + And I set up a fake editor replacing "1.txt" by "2.txt" + And I run :edit-url -p + Then data/numbers/2.txt should be loaded + And the session should look like: + windows: + - tabs: + - active: true + history: + - active: true + url: http://localhost:*/data/numbers/1.txt + - tabs: + - active: true + history: + - active: true + url: http://localhost:*/data/numbers/2.txt + private: true + Scenario: Editing a URL with -t and -b When I run :edit-url -t -b Then the error "Only one of -t/-b/-w can be given!" should be shown From 879e8dfb2c879c9c1cb7cb90baade6f027f3cf46 Mon Sep 17 00:00:00 2001 From: Ryan Farley Date: Thu, 26 Oct 2017 17:09:45 -0500 Subject: [PATCH 52/89] fix D401 in importer --- scripts/importer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/importer.py b/scripts/importer.py index 436971e58..9171bbbb6 100755 --- a/scripts/importer.py +++ b/scripts/importer.py @@ -138,7 +138,7 @@ def get_args(): def search_escape(url): - """Escapes URLs such that preexisting { and } are handled properly. + """Escape URLs such that preexisting { and } are handled properly. Will obviously trash a properly-formatted Qutebrowser URL. """ From 97d719b1792e419aa53e36467d9e0186d71bbf75 Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Thu, 26 Oct 2017 17:56:28 -0400 Subject: [PATCH 53/89] Add a simple benchmark for _update_tab_titles --- tests/unit/mainwindow/test_tabwidget.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/unit/mainwindow/test_tabwidget.py b/tests/unit/mainwindow/test_tabwidget.py index e05e6a164..14b35bc03 100644 --- a/tests/unit/mainwindow/test_tabwidget.py +++ b/tests/unit/mainwindow/test_tabwidget.py @@ -52,3 +52,21 @@ class TestTabWidget: with qtbot.waitExposed(widget): widget.show() + + def test_update_tab_titles_benchmark(self, benchmark, widget, + qtbot, fake_web_tab): + """Benchmark for update_tab_titles.""" + widget.addTab(fake_web_tab(), 'foobar') + widget.addTab(fake_web_tab(), 'foobar2') + widget.addTab(fake_web_tab(), 'foobar3') + widget.addTab(fake_web_tab(), 'foobar4') + + with qtbot.waitExposed(widget): + widget.show() + + def bench(): + for _a in range(1000): + # pylint: disable=protected-access + widget._update_tab_titles() + # pylint: enable=protected-access + benchmark(bench) From 24f466b2c35441c40179dde89279225fa1fc0fb9 Mon Sep 17 00:00:00 2001 From: Ryan Roden-Corrent Date: Thu, 26 Oct 2017 22:13:35 -0400 Subject: [PATCH 54/89] Add --related flag to edit-url. --- qutebrowser/browser/commands.py | 14 +++++++++----- tests/end2end/features/editor.feature | 10 ++++++++++ 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 6c8394771..93956157b 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -2113,7 +2113,7 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', scope='window') def edit_url(self, url=None, bg=False, tab=False, window=False, - private=False): + private=False, related=False): """Navigate to a url formed in an external editor. The editor which should be launched can be configured via the @@ -2125,6 +2125,8 @@ class CommandDispatcher: tab: Open in a new tab. window: Open in a new window. private: Open a new window in private browsing mode. + related: If opening a new tab, position the tab as related to the + current one (like clicking on a link). """ cmdutils.check_exclusive((tab, bg, window), 'tbw') @@ -2135,7 +2137,7 @@ class CommandDispatcher: # Passthrough for openurl args (e.g. -t, -b, -w) ed.editing_finished.connect(functools.partial( self._open_if_changed, old_url=old_url, bg=bg, tab=tab, - window=window, private=private)) + window=window, private=private, related=related)) ed.edit(url or old_url) @@ -2160,7 +2162,7 @@ class CommandDispatcher: self._tabbed_browser.jump_mark(key) def _open_if_changed(self, url=None, old_url=None, bg=False, tab=False, - window=False, private=False): + window=False, private=False, related=False): """Open a URL unless it's already open in the tab. Args: @@ -2170,10 +2172,12 @@ class CommandDispatcher: tab: Open in a new tab. window: Open in a new window. private: Open a new window in private browsing mode. + related: If opening a new tab, position the tab as related to the + current one (like clicking on a link). """ - if bg or tab or window or private or url != old_url: + if bg or tab or window or private or related or url != old_url: self.openurl(url=url, bg=bg, tab=tab, window=window, - private=private) + private=private, related=related) @cmdutils.register(instance='command-dispatcher', scope='window') def fullscreen(self, leave=False): diff --git a/tests/end2end/features/editor.feature b/tests/end2end/features/editor.feature index dbbc06879..d3c634b0d 100644 --- a/tests/end2end/features/editor.feature +++ b/tests/end2end/features/editor.feature @@ -23,6 +23,16 @@ Feature: Opening external editors - data/numbers/1.txt - data/numbers/2.txt (active) + Scenario: Editing a URL with -rt + When I set tabs.new_position.related to prev + And I open data/numbers/1.txt + And I set up a fake editor replacing "1.txt" by "2.txt" + And I run :edit-url -rt + Then data/numbers/2.txt should be loaded + And the following tabs should be open: + - data/numbers/2.txt (active) + - data/numbers/1.txt + Scenario: Editing a URL with -b When I run :tab-only And I open data/numbers/1.txt From 8f068dda1b2cef8041d9b082190d235a7b63632d Mon Sep 17 00:00:00 2001 From: Ryan Roden-Corrent Date: Fri, 27 Oct 2017 07:23:41 -0400 Subject: [PATCH 55/89] Disable pylint's too-many-boolean-expressions. --- .pylintrc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.pylintrc b/.pylintrc index bd988944c..443979bcd 100644 --- a/.pylintrc +++ b/.pylintrc @@ -40,7 +40,8 @@ disable=no-self-use, # https://github.com/PyCQA/pylint/issues/1698 unsupported-membership-test, unsupported-assignment-operation, - unsubscriptable-object + unsubscriptable-object, + too-many-boolean-expressions [BASIC] function-rgx=[a-z_][a-z0-9_]{2,50}$ From 2947b75ab9ce0f1d2852d0f602225414de0767a5 Mon Sep 17 00:00:00 2001 From: Luca Benci Date: Fri, 27 Oct 2017 19:52:10 +0200 Subject: [PATCH 56/89] Make eslint happy --- qutebrowser/javascript/webelem.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/qutebrowser/javascript/webelem.js b/qutebrowser/javascript/webelem.js index 283f772b5..0a23ad543 100644 --- a/qutebrowser/javascript/webelem.js +++ b/qutebrowser/javascript/webelem.js @@ -52,12 +52,15 @@ window._qutebrowser.webelem = (function() { var caret_position = 0; try { caret_position = elem.selectionStart; - } catch (e) { - if (e instanceof DOMException && e.name === "InvalidStateError") { + } catch (err) { + if ( + err instanceof DOMException && + err.name === "InvalidStateError" + ) { // nothing to do, caret_position is already 0 } else { // not the droid we're looking for - throw e + throw err; } } From f5f11f7f4e6786ef2fa22258c5ed520fe661393e Mon Sep 17 00:00:00 2001 From: Luca Benci Date: Fri, 27 Oct 2017 20:15:33 +0200 Subject: [PATCH 57/89] Remove `_ignore_change` --- qutebrowser/completion/completer.py | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/qutebrowser/completion/completer.py b/qutebrowser/completion/completer.py index 0840d6396..f0efd6751 100644 --- a/qutebrowser/completion/completer.py +++ b/qutebrowser/completion/completer.py @@ -43,7 +43,6 @@ class Completer(QObject): Attributes: _cmd: The statusbar Command object this completer belongs to. - _ignore_change: Whether to ignore the next completion update. _timer: The timer used to trigger the completion update. _last_cursor_pos: The old cursor position so we avoid double completion updates. @@ -54,7 +53,6 @@ class Completer(QObject): def __init__(self, cmd, parent=None): super().__init__(parent) self._cmd = cmd - self._ignore_change = False self._timer = QTimer() self._timer.setSingleShot(True) self._timer.setInterval(0) @@ -178,13 +176,14 @@ class Completer(QObject): text = self._quote(text) model = self._model() if model.count() == 1 and config.val.completion.quick: - # If we only have one item, we want to apply it immediately - # and go on to the next part. - self._change_completed_part(text, before, after, immediate=True) + # If we only have one item, we want to apply it immediately and go + # on to the next part, unless we are quick-completing the part + # after maxsplit, so that we don't keep offering completions + # (see issue #1519) if maxsplit is not None and maxsplit < len(before): - # If we are quick-completing the part after maxsplit, don't - # keep offering completions (see issue #1519) - self._ignore_change = True + self._change_completed_part(text, before, after) + else: + self._change_completed_part(text, before, after, immediate=True) else: self._change_completed_part(text, before, after) @@ -219,12 +218,6 @@ class Completer(QObject): @pyqtSlot() def _update_completion(self): """Check if completions are available and activate them.""" - if self._ignore_change: - log.completion.debug("Ignoring completion update because " - "ignore_change is True.") - self._ignore_change = False - return - completion = self.parent() if self._cmd.prefix() != ':': From 249164eb9b565eb54d37655737f2b0d9a00e7544 Mon Sep 17 00:00:00 2001 From: Luca Benci Date: Fri, 27 Oct 2017 22:25:41 +0200 Subject: [PATCH 58/89] Fix `test_quickcomplete_flicker` The test needed to be fixed because of how the completer behaviour changed. Before: completer always scheduled a completion update on selection changed, but the updates themselves were ignored if not needed. Now: completer only schedules completion updates when actually needed, but never ignores a completion update. So, before it was correct to check whether `set_model` was called, now we must check if the completion was actually scheduled. This can be done by checking the parameters with which `_change_completed_part` is called, since a completion is scheduled only when `immediate=True` --- tests/unit/completion/test_completer.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/unit/completion/test_completer.py b/tests/unit/completion/test_completer.py index 8575cf02d..5f1581aa0 100644 --- a/tests/unit/completion/test_completer.py +++ b/tests/unit/completion/test_completer.py @@ -299,6 +299,12 @@ def test_quickcomplete_flicker(status_command_stub, completer_obj, config_stub.val.completion.quick = True _set_cmd_prompt(status_command_stub, ':open |') - completer_obj.on_selection_changed('http://example.com') - completer_obj.schedule_completion_update() - assert not completion_widget_stub.set_model.called + + url = 'http://example.com' + completer_obj._change_completed_part = unittest.mock.Mock() + completer_obj.on_selection_changed(url) + + # no immediate (default is false) + completer_obj._change_completed_part.assert_called_with(url, # text + ['open'], # before + []) # after From 2a4163b2c7b3e8bb321ba92e01e73af2957288dc Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Fri, 27 Oct 2017 17:20:55 -0400 Subject: [PATCH 59/89] Fix ellipsis on pinned tabs with index > 10 See #3209 --- qutebrowser/mainwindow/tabwidget.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py index 67d138fd4..c3b7436d7 100644 --- a/qutebrowser/mainwindow/tabwidget.py +++ b/qutebrowser/mainwindow/tabwidget.py @@ -455,7 +455,7 @@ class TabBar(QTabBar): """ icon = self.tabIcon(index) extent = self.style().pixelMetric(QStyle.PM_TabBarIconSize, None, self) - if not icon: + if icon.isNull(): icon_width = 0 else: icon_width = icon.actualSize(QSize(extent, extent)).width() From 3d53ffb7ef586177f5265360f656d81a71cc9abf Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 30 Oct 2017 06:55:55 +0100 Subject: [PATCH 60/89] Bump up YAML load deadline some more --- qutebrowser/utils/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/utils/utils.py b/qutebrowser/utils/utils.py index 018a0f18a..f59c9f553 100644 --- a/qutebrowser/utils/utils.py +++ b/qutebrowser/utils/utils.py @@ -882,7 +882,7 @@ def yaml_load(f): end = datetime.datetime.now() delta = (end - start).total_seconds() - deadline = 3 if 'CI' in os.environ else 2 + deadline = 5 if 'CI' in os.environ else 2 if delta > deadline: # pragma: no cover log.misc.warning( "YAML load took unusually long, please report this at " From 08965399c541b0124d8dc18e70affa7934ca1add Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 30 Oct 2017 06:59:16 +0100 Subject: [PATCH 61/89] Update changelog --- doc/changelog.asciidoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 64ae5405c..9a78243c6 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -32,6 +32,8 @@ Added - New `config.source(...)` method for `config.py` to source another file. - New `keyhint.radius` option to configure the edge rounding for the key hint widget. +- `:edit-url` now handles the `--private` and `--related` flags, which have the + same effect they have with `:open`. Changed ~~~~~~~ From 9d415587bca6bac4dc25336dc38957830d04e2a3 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 30 Oct 2017 07:05:54 +0100 Subject: [PATCH 62/89] Mark flaky test as flaky --- tests/end2end/features/tabs.feature | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/end2end/features/tabs.feature b/tests/end2end/features/tabs.feature index be82d85bd..14461317b 100644 --- a/tests/end2end/features/tabs.feature +++ b/tests/end2end/features/tabs.feature @@ -713,6 +713,7 @@ Feature: Tab management Then the following tabs should be open: - data/hello.txt (active) + @flaky Scenario: Double-undo with single tab on tabs.last_close default page Given I have a fresh instance When I open about:blank From 43aa7423abb931ddcbccc51411f6a8a16bf056b5 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 30 Oct 2017 08:26:47 +0100 Subject: [PATCH 63/89] Update docs --- doc/help/commands.asciidoc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc index fed4e88d4..5706a6bfb 100644 --- a/doc/help/commands.asciidoc +++ b/doc/help/commands.asciidoc @@ -361,7 +361,7 @@ The index of the download to retry. [[edit-url]] === edit-url -Syntax: +:edit-url [*--bg*] [*--tab*] [*--window*] ['url']+ +Syntax: +:edit-url [*--bg*] [*--tab*] [*--window*] [*--private*] [*--related*] ['url']+ Navigate to a url formed in an external editor. @@ -374,6 +374,9 @@ The editor which should be launched can be configured via the `editor.command` c * +*-b*+, +*--bg*+: Open in a new background tab. * +*-t*+, +*--tab*+: Open in a new tab. * +*-w*+, +*--window*+: Open in a new window. +* +*-p*+, +*--private*+: Open a new window in private browsing mode. +* +*-r*+, +*--related*+: If opening a new tab, position the tab as related to the current one (like clicking on a link). + [[fake-key]] === fake-key From 64b6852ae34929659f2376c4fd02545a012007bd Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Mon, 30 Oct 2017 12:12:57 -0400 Subject: [PATCH 64/89] Fix a couple style issues --- qutebrowser/mainwindow/tabwidget.py | 3 +-- qutebrowser/misc/utilcmds.py | 4 ++-- tests/unit/mainwindow/test_tabwidget.py | 7 +------ 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py index c3b7436d7..6384605fc 100644 --- a/qutebrowser/mainwindow/tabwidget.py +++ b/qutebrowser/mainwindow/tabwidget.py @@ -488,8 +488,7 @@ class TabBar(QTabBar): def _pinned_statistics(self) -> (int, int): """Get the number of pinned tabs and the total width of pinned tabs.""" - pinned_list = [idx - for idx in range(self.count()) + pinned_list = [idx for idx in range(self.count()) if self._tab_pinned(idx)] pinned_count = len(pinned_list) pinned_width = sum(self.minimumTabSizeHint(idx, ellipsis=False).width() diff --git a/qutebrowser/misc/utilcmds.py b/qutebrowser/misc/utilcmds.py index 07949b771..d52793669 100644 --- a/qutebrowser/misc/utilcmds.py +++ b/qutebrowser/misc/utilcmds.py @@ -185,8 +185,8 @@ def debug_cache_stats(): tabbed_browser = objreg.get('tabbed-browser', scope='window', window='last-focused') # pylint: disable=protected-access - tabbed_browser_info = tabbed_browser.tabBar(). \ - _minimum_tab_size_hint_helper.cache_info() + tab_bar = tabbed_browser.tabBar() + tabbed_browser_info = tab_bar._minimum_tab_size_hint_helper.cache_info() # pylint: enable=protected-access log.misc.debug('is_valid_prefix: {}'.format(prefix_info)) diff --git a/tests/unit/mainwindow/test_tabwidget.py b/tests/unit/mainwindow/test_tabwidget.py index 14b35bc03..dd87c7ce2 100644 --- a/tests/unit/mainwindow/test_tabwidget.py +++ b/tests/unit/mainwindow/test_tabwidget.py @@ -64,9 +64,4 @@ class TestTabWidget: with qtbot.waitExposed(widget): widget.show() - def bench(): - for _a in range(1000): - # pylint: disable=protected-access - widget._update_tab_titles() - # pylint: enable=protected-access - benchmark(bench) + benchmark(widget._update_tab_titles) From dad7e7b9d211c29e338b6fe2ecfa51d3133edf5b Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 31 Oct 2017 07:06:04 +0100 Subject: [PATCH 65/89] Update changelog --- doc/changelog.asciidoc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 9a78243c6..f4e31d5e8 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -67,6 +67,11 @@ Removed v1.0.3 (unreleased) ------------------- +Changed +~~~~~~~ + +- Performance improvements for tab rendering + Fixed ~~~~~ From 0b86b302a2c4ed5d03d50ea282e96bb9ddc7b3c8 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 31 Oct 2017 07:35:00 +0100 Subject: [PATCH 66/89] pylint: Turn off some more too-many-* stuff globally Humans are just better at judging what's okay here than a machine. --- .pylintrc | 5 ++++- qutebrowser/app.py | 1 - qutebrowser/commands/command.py | 2 -- qutebrowser/misc/split.py | 1 - scripts/asciidoc2html.py | 1 - tests/unit/browser/webkit/test_webkitelem.py | 1 - tests/unit/config/test_configinit.py | 1 - 7 files changed, 4 insertions(+), 8 deletions(-) diff --git a/.pylintrc b/.pylintrc index 443979bcd..bca11bf80 100644 --- a/.pylintrc +++ b/.pylintrc @@ -41,7 +41,10 @@ disable=no-self-use, unsupported-membership-test, unsupported-assignment-operation, unsubscriptable-object, - too-many-boolean-expressions + too-many-boolean-expressions, + too-many-locals, + too-many-branches, + too-many-statements [BASIC] function-rgx=[a-z_][a-z0-9_]{2,50}$ diff --git a/qutebrowser/app.py b/qutebrowser/app.py index 7fb87014e..2ed579f61 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -417,7 +417,6 @@ def _init_modules(args, crash_handler): args: The argparse namespace. crash_handler: The CrashHandler instance. """ - # pylint: disable=too-many-statements log.init.debug("Initializing save manager...") save_manager = savemanager.SaveManager(qApp) objreg.register('save-manager', save_manager) diff --git a/qutebrowser/commands/command.py b/qutebrowser/commands/command.py index 9baa4efe7..43a8824d1 100644 --- a/qutebrowser/commands/command.py +++ b/qutebrowser/commands/command.py @@ -81,8 +81,6 @@ class Command: deprecated=False, no_cmd_split=False, star_args_optional=False, scope='global', backend=None, no_replace_variables=False): - # I really don't know how to solve this in a better way, I tried. - # pylint: disable=too-many-locals if modes is not None and not_modes is not None: raise ValueError("Only modes or not_modes can be given!") if modes is not None: diff --git a/qutebrowser/misc/split.py b/qutebrowser/misc/split.py index 3f3b2d362..045d9fe4b 100644 --- a/qutebrowser/misc/split.py +++ b/qutebrowser/misc/split.py @@ -57,7 +57,6 @@ class ShellLexer: def __iter__(self): # pragma: no mccabe """Read a raw token from the input stream.""" - # pylint: disable=too-many-branches,too-many-statements self.reset() for nextchar in self.string: if self.state == ' ': diff --git a/scripts/asciidoc2html.py b/scripts/asciidoc2html.py index 0055c5a42..51deb009f 100755 --- a/scripts/asciidoc2html.py +++ b/scripts/asciidoc2html.py @@ -117,7 +117,6 @@ class AsciiDoc: def _build_website_file(self, root, filename): """Build a single website file.""" - # pylint: disable=too-many-locals,too-many-statements src = os.path.join(root, filename) src_basename = os.path.basename(src) parts = [self._args.website[0]] diff --git a/tests/unit/browser/webkit/test_webkitelem.py b/tests/unit/browser/webkit/test_webkitelem.py index 3174d7281..c64756eb5 100644 --- a/tests/unit/browser/webkit/test_webkitelem.py +++ b/tests/unit/browser/webkit/test_webkitelem.py @@ -53,7 +53,6 @@ def get_webelem(geometry=None, frame=None, *, null=False, style=None, evaluateJavaScript. zoom_text_only: Whether zoom.text_only is set in the config """ - # pylint: disable=too-many-locals,too-many-branches elem = mock.Mock() elem.isNull.return_value = null elem.geometry.return_value = geometry diff --git a/tests/unit/config/test_configinit.py b/tests/unit/config/test_configinit.py index 1501b794d..4923b0765 100644 --- a/tests/unit/config/test_configinit.py +++ b/tests/unit/config/test_configinit.py @@ -111,7 +111,6 @@ class TestEarlyInit: def test_autoconfig_yml(self, init_patch, config_tmpdir, caplog, args, load_autoconfig, config_py, invalid_yaml): """Test interaction between config.py and autoconfig.yml.""" - # pylint: disable=too-many-locals,too-many-branches # Prepare files autoconfig_file = config_tmpdir / 'autoconfig.yml' config_py_file = config_tmpdir / 'config.py' From d4d791f14e7fe573b510d59d78d43a7b335f2e4e Mon Sep 17 00:00:00 2001 From: plexigras Date: Tue, 31 Oct 2017 11:53:35 +0100 Subject: [PATCH 67/89] es6ified js --- qutebrowser/javascript/.eslintrc.yaml | 3 +- qutebrowser/javascript/history.js | 54 ++++++------ qutebrowser/javascript/pac_utils.js | 104 +++++++++++------------ qutebrowser/javascript/position_caret.js | 31 ++++--- qutebrowser/javascript/scroll.js | 20 ++--- qutebrowser/javascript/webelem.js | 78 ++++++++--------- 6 files changed, 144 insertions(+), 146 deletions(-) diff --git a/qutebrowser/javascript/.eslintrc.yaml b/qutebrowser/javascript/.eslintrc.yaml index d75ef11d8..d82c516d5 100644 --- a/qutebrowser/javascript/.eslintrc.yaml +++ b/qutebrowser/javascript/.eslintrc.yaml @@ -2,7 +2,7 @@ env: browser: true parserOptions: - ecmaVersion: 3 + ecmaVersion: 6 extends: "eslint:all" @@ -13,7 +13,6 @@ rules: padded-blocks: ["error", "never"] space-before-function-paren: ["error", "never"] no-underscore-dangle: "off" - no-var: "off" vars-on-top: "off" newline-after-var: "off" camelcase: "off" diff --git a/qutebrowser/javascript/history.js b/qutebrowser/javascript/history.js index edc51ae01..ba25c8c00 100644 --- a/qutebrowser/javascript/history.js +++ b/qutebrowser/javascript/history.js @@ -19,24 +19,24 @@ "use strict"; -window.loadHistory = (function() { +window.loadHistory = (() => { // Date of last seen item. - var lastItemDate = null; + let lastItemDate = null; // Each request for new items includes the time of the last item and an // offset. The offset is equal to the number of items from the previous // request that had time=nextTime, and causes the next request to skip // those items to avoid duplicates. - var nextTime = null; - var nextOffset = 0; + let nextTime = null; + let nextOffset = 0; // The URL to fetch data from. - var DATA_URL = "qute://history/data"; + const DATA_URL = "qute://history/data"; // Various fixed elements - var EOF_MESSAGE = document.getElementById("eof"); - var LOAD_LINK = document.getElementById("load"); - var HIST_CONTAINER = document.getElementById("hist-container"); + const EOF_MESSAGE = document.getElementById("eof"); + const LOAD_LINK = document.getElementById("load"); + const HIST_CONTAINER = document.getElementById("hist-container"); /** * Finds or creates the session table>tbody to which item with given date @@ -47,17 +47,17 @@ window.loadHistory = (function() { */ function getSessionNode(date) { // Find/create table - var tableId = ["hist", date.getDate(), date.getMonth(), + const tableId = ["hist", date.getDate(), date.getMonth(), date.getYear()].join("-"); - var table = document.getElementById(tableId); + let table = document.getElementById(tableId); if (table === null) { table = document.createElement("table"); table.id = tableId; // Caption contains human-readable date - var caption = document.createElement("caption"); + const caption = document.createElement("caption"); caption.className = "date"; - var options = { + const options = { "weekday": "long", "year": "numeric", "month": "long", @@ -71,7 +71,7 @@ window.loadHistory = (function() { } // Find/create tbody - var tbody = table.lastChild; + let tbody = table.lastChild; if (tbody.tagName !== "TBODY") { tbody = document.createElement("tbody"); table.appendChild(tbody); @@ -80,10 +80,10 @@ window.loadHistory = (function() { // Create session-separator and new tbody if necessary if (tbody.lastChild !== null && lastItemDate !== null && window.GAP_INTERVAL > 0) { - var interval = lastItemDate.getTime() - date.getTime(); + const interval = lastItemDate.getTime() - date.getTime(); if (interval > window.GAP_INTERVAL) { // Add session-separator - var sessionSeparator = document.createElement("td"); + const sessionSeparator = document.createElement("td"); sessionSeparator.className = "session-separator"; sessionSeparator.colSpan = 2; sessionSeparator.innerHTML = "§"; @@ -108,20 +108,20 @@ window.loadHistory = (function() { * @returns {Element} the completed tr. */ function makeHistoryRow(itemUrl, itemTitle, itemTime) { - var row = document.createElement("tr"); + const row = document.createElement("tr"); - var title = document.createElement("td"); + const title = document.createElement("td"); title.className = "title"; - var link = document.createElement("a"); + const link = document.createElement("a"); link.href = itemUrl; link.innerHTML = itemTitle; - var host = document.createElement("span"); + const host = document.createElement("span"); host.className = "hostname"; host.innerHTML = link.hostname; title.appendChild(link); title.appendChild(host); - var time = document.createElement("td"); + const time = document.createElement("td"); time.className = "time"; time.innerHTML = itemTime; @@ -139,11 +139,11 @@ window.loadHistory = (function() { * @returns {void} */ function getJSON(url, callback) { - var xhr = new XMLHttpRequest(); + const xhr = new XMLHttpRequest(); xhr.open("GET", url, true); xhr.responseType = "json"; - xhr.onload = function() { - var status = xhr.status; + xhr.onload = () => { + const status = xhr.status; callback(status, xhr.response); }; xhr.send(); @@ -172,10 +172,10 @@ window.loadHistory = (function() { nextTime = history[history.length - 1].time; nextOffset = 0; - for (var i = 0, len = history.length; i < len; i++) { - var item = history[i]; + for (let i = 0, len = history.length; i < len; i++) { + const item = history[i]; // python's time.time returns seconds, but js Date expects ms - var currentItemDate = new Date(item.time * 1000); + const currentItemDate = new Date(item.time * 1000); getSessionNode(currentItemDate).appendChild(makeHistoryRow( item.url, item.title, currentItemDate.toLocaleTimeString() )); @@ -191,7 +191,7 @@ window.loadHistory = (function() { * @return {void} */ function loadHistory() { - var url = DATA_URL.concat("?offset=", nextOffset.toString()); + let url = DATA_URL.concat("?offset=", nextOffset.toString()); if (nextTime === null) { getJSON(url, receiveHistory); } else { diff --git a/qutebrowser/javascript/pac_utils.js b/qutebrowser/javascript/pac_utils.js index 0aba4c070..d77a5049d 100644 --- a/qutebrowser/javascript/pac_utils.js +++ b/qutebrowser/javascript/pac_utils.js @@ -39,7 +39,7 @@ /* Script for Proxy Auto Config in the new world order. - - Gagan Saksena 04/24/00 + - Gagan Saksena 04/24/00 */ function dnsDomainIs(host, domain) { @@ -52,8 +52,8 @@ function dnsDomainLevels(host) { } function convert_addr(ipchars) { - var bytes = ipchars.split('.'); - var result = ((bytes[0] & 0xff) << 24) | + const bytes = ipchars.split('.'); + const result = ((bytes[0] & 0xff) << 24) | ((bytes[1] & 0xff) << 16) | ((bytes[2] & 0xff) << 8) | (bytes[3] & 0xff); @@ -61,7 +61,7 @@ function convert_addr(ipchars) { } function isInNet(ipaddr, pattern, maskstr) { - var test = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/ + const test = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/ .exec(ipaddr); if (test == null) { ipaddr = dnsResolve(ipaddr); @@ -71,9 +71,9 @@ function isInNet(ipaddr, pattern, maskstr) { test[3] > 255 || test[4] > 255) { return false; // not an IP address } - var host = convert_addr(ipaddr); - var pat = convert_addr(pattern); - var mask = convert_addr(maskstr); + const host = convert_addr(ipaddr); + const pat = convert_addr(pattern); + const mask = convert_addr(maskstr); return ((host & mask) == (pat & mask)); } @@ -82,75 +82,75 @@ function isPlainHostName(host) { } function isResolvable(host) { - var ip = dnsResolve(host); + const ip = dnsResolve(host); return (ip != null); } function localHostOrDomainIs(host, hostdom) { return (host == hostdom) || - (hostdom.lastIndexOf(host + '.', 0) == 0); + (hostdom.lastIndexOf(`${host}.`, 0) == 0); } function shExpMatch(url, pattern) { pattern = pattern.replace(/\./g, '\\.'); pattern = pattern.replace(/\*/g, '.*'); pattern = pattern.replace(/\?/g, '.'); - var newRe = new RegExp('^'+pattern+'$'); + const newRe = new RegExp(`^${pattern}$`); return newRe.test(url); } -var wdays = {SUN: 0, MON: 1, TUE: 2, WED: 3, THU: 4, FRI: 5, SAT: 6}; +const wdays = {SUN: 0, MON: 1, TUE: 2, WED: 3, THU: 4, FRI: 5, SAT: 6}; -var months = {JAN: 0, FEB: 1, MAR: 2, APR: 3, MAY: 4, JUN: 5, JUL: 6, +const months = {JAN: 0, FEB: 1, MAR: 2, APR: 3, MAY: 4, JUN: 5, JUL: 6, AUG: 7, SEP: 8, OCT: 9, NOV: 10, DEC: 11}; -function weekdayRange() { +function weekdayRange(...args) { function getDay(weekday) { if (weekday in wdays) { return wdays[weekday]; } return -1; } - var date = new Date(); - var argc = arguments.length; - var wday; + const date = new Date(); + let argc = args.length; + let wday; if (argc < 1) return false; - if (arguments[argc - 1] == 'GMT') { + if (args[argc - 1] == 'GMT') { argc--; wday = date.getUTCDay(); } else { wday = date.getDay(); } - var wd1 = getDay(arguments[0]); - var wd2 = (argc == 2) ? getDay(arguments[1]) : wd1; + const wd1 = getDay(args[0]); + const wd2 = (argc == 2) ? getDay(args[1]) : wd1; return (wd1 == -1 || wd2 == -1) ? false : (wd1 <= wday && wday <= wd2); } -function dateRange() { +function dateRange(...args) { function getMonth(name) { if (name in months) { return months[name]; } return -1; } - var date = new Date(); - var argc = arguments.length; + let date = new Date(); + let argc = args.length; if (argc < 1) { return false; } - var isGMT = (arguments[argc - 1] == 'GMT'); + const isGMT = (args[argc - 1] == 'GMT'); if (isGMT) { argc--; } // function will work even without explict handling of this case if (argc == 1) { - var tmp = parseInt(arguments[0]); + let tmp = parseInt(args[0]); if (isNaN(tmp)) { - return ((isGMT ? date.getUTCMonth() : date.getMonth()) == - getMonth(arguments[0])); + return (isGMT ? date.getUTCMonth() : date.getMonth()) == + getMonth(args[0]); } else if (tmp < 32) { return ((isGMT ? date.getUTCDate() : date.getDate()) == tmp); } else { @@ -158,15 +158,15 @@ function dateRange() { tmp); } } - var year = date.getFullYear(); - var date1, date2; + const year = date.getFullYear(); + let date1, date2; date1 = new Date(year, 0, 1, 0, 0, 0); date2 = new Date(year, 11, 31, 23, 59, 59); - var adjustMonth = false; - for (var i = 0; i < (argc >> 1); i++) { - var tmp = parseInt(arguments[i]); + let adjustMonth = false; + for (let i = 0; i < (argc >> 1); i++) { + let tmp = parseInt(args[i]); if (isNaN(tmp)) { - var mon = getMonth(arguments[i]); + let mon = getMonth(args[i]); date1.setMonth(mon); } else if (tmp < 32) { adjustMonth = (argc <= 2); @@ -175,10 +175,10 @@ function dateRange() { date1.setFullYear(tmp); } } - for (var i = (argc >> 1); i < argc; i++) { - var tmp = parseInt(arguments[i]); + for (let i = (argc >> 1); i < argc; i++) { + let tmp = parseInt(args[i]); if (isNaN(tmp)) { - var mon = getMonth(arguments[i]); + let mon = getMonth(args[i]); date2.setMonth(mon); } else if (tmp < 32) { date2.setDate(tmp); @@ -191,7 +191,7 @@ function dateRange() { date2.setMonth(date.getMonth()); } if (isGMT) { - var tmp = date; + let tmp = date; tmp.setFullYear(date.getUTCFullYear()); tmp.setMonth(date.getUTCMonth()); tmp.setDate(date.getUTCDate()); @@ -203,39 +203,39 @@ function dateRange() { return ((date1 <= date) && (date <= date2)); } -function timeRange() { - var argc = arguments.length; - var date = new Date(); - var isGMT= false; +function timeRange(...args) { + let argc = args.length; + const date = new Date(); + let isGMT= false; if (argc < 1) { return false; } - if (arguments[argc - 1] == 'GMT') { + if (args[argc - 1] == 'GMT') { isGMT = true; argc--; } - var hour = isGMT ? date.getUTCHours() : date.getHours(); - var date1, date2; + const hour = isGMT ? date.getUTCHours() : date.getHours(); + let date1, date2; date1 = new Date(); date2 = new Date(); if (argc == 1) { - return (hour == arguments[0]); + return hour == args[0]; } else if (argc == 2) { - return ((arguments[0] <= hour) && (hour <= arguments[1])); + return (args[0] <= hour) && (hour <= args[1]); } else { switch (argc) { case 6: - date1.setSeconds(arguments[2]); - date2.setSeconds(arguments[5]); + date1.setSeconds(args[2]); + date2.setSeconds(args[5]); case 4: - var middle = argc >> 1; - date1.setHours(arguments[0]); - date1.setMinutes(arguments[1]); - date2.setHours(arguments[middle]); - date2.setMinutes(arguments[middle + 1]); + const middle = argc >> 1; + date1.setHours(args[0]); + date1.setMinutes(args[1]); + date2.setHours(args[middle]); + date2.setMinutes(args[middle + 1]); if (middle == 2) { date2.setSeconds(59); } diff --git a/qutebrowser/javascript/position_caret.js b/qutebrowser/javascript/position_caret.js index c2df1cf1d..13203b781 100644 --- a/qutebrowser/javascript/position_caret.js +++ b/qutebrowser/javascript/position_caret.js @@ -27,16 +27,15 @@ */ "use strict"; - -(function() { +(() => { // FIXME:qtwebengine integrate this with other window._qutebrowser code? function isElementInViewport(node) { // eslint-disable-line complexity - var i; - var boundingRect = (node.getClientRects()[0] || + let i; + let boundingRect = (node.getClientRects()[0] || node.getBoundingClientRect()); if (boundingRect.width <= 1 && boundingRect.height <= 1) { - var rects = node.getClientRects(); + const rects = node.getClientRects(); for (i = 0; i < rects.length; i++) { if (rects[i].width > rects[0].height && rects[i].height > rects[0].height) { @@ -51,8 +50,8 @@ return null; } if (boundingRect.width <= 1 || boundingRect.height <= 1) { - var children = node.children; - var visibleChildNode = false; + const children = node.children; + let visibleChildNode = false; for (i = 0; i < children.length; ++i) { boundingRect = (children[i].getClientRects()[0] || children[i].getBoundingClientRect()); @@ -69,7 +68,7 @@ boundingRect.left + boundingRect.width < -10) { return null; } - var computedStyle = window.getComputedStyle(node, null); + const computedStyle = window.getComputedStyle(node, null); if (computedStyle.visibility !== "visible" || computedStyle.display === "none" || node.hasAttribute("disabled") || @@ -81,27 +80,27 @@ } function positionCaret() { - var walker = document.createTreeWalker(document.body, 4, null); - var node; - var textNodes = []; - var el; + const walker = document.createTreeWalker(document.body, 4, null); + let node; + const textNodes = []; + let el; while ((node = walker.nextNode())) { if (node.nodeType === 3 && node.data.trim() !== "") { textNodes.push(node); } } - for (var i = 0; i < textNodes.length; i++) { - var element = textNodes[i].parentElement; + for (let i = 0; i < textNodes.length; i++) { + const element = textNodes[i].parentElement; if (isElementInViewport(element.parentElement)) { el = element; break; } } if (el !== undefined) { - var range = document.createRange(); + const range = document.createRange(); range.setStart(el, 0); range.setEnd(el, 0); - var sel = window.getSelection(); + const sel = window.getSelection(); sel.removeAllRanges(); sel.addRange(range); } diff --git a/qutebrowser/javascript/scroll.js b/qutebrowser/javascript/scroll.js index 188320f11..97713367a 100644 --- a/qutebrowser/javascript/scroll.js +++ b/qutebrowser/javascript/scroll.js @@ -19,20 +19,20 @@ "use strict"; -window._qutebrowser.scroll = (function() { - var funcs = {}; +window._qutebrowser.scroll = (() => { + const funcs = {}; - funcs.to_perc = function(x, y) { - var x_px = window.scrollX; - var y_px = window.scrollY; + funcs.to_perc = (x, y) => { + let x_px = window.scrollX; + let y_px = window.scrollY; - var width = Math.max( + const width = Math.max( document.body.scrollWidth, document.body.offsetWidth, document.documentElement.scrollWidth, document.documentElement.offsetWidth ); - var height = Math.max( + const height = Math.max( document.body.scrollHeight, document.body.offsetHeight, document.documentElement.scrollHeight, @@ -65,9 +65,9 @@ window._qutebrowser.scroll = (function() { window.scroll(x_px, y_px); }; - funcs.delta_page = function(x, y) { - var dx = window.innerWidth * x; - var dy = window.innerHeight * y; + funcs.delta_page = (x, y) => { + const dx = window.innerWidth * x; + const dy = window.innerHeight * y; window.scrollBy(dx, dy); }; diff --git a/qutebrowser/javascript/webelem.js b/qutebrowser/javascript/webelem.js index ce348c46b..5f4a4b5e5 100644 --- a/qutebrowser/javascript/webelem.js +++ b/qutebrowser/javascript/webelem.js @@ -36,19 +36,19 @@ "use strict"; -window._qutebrowser.webelem = (function() { - var funcs = {}; - var elements = []; +window._qutebrowser.webelem = (() => { + const funcs = {}; + const elements = []; function serialize_elem(elem) { if (!elem) { return null; } - var id = elements.length; + const id = elements.length; elements[id] = elem; - var out = { + const out = { "id": id, "value": elem.value, "outer_xml": elem.outerHTML, @@ -77,16 +77,16 @@ window._qutebrowser.webelem = (function() { out.text = elem.text; } // else: don't add the text at all - var attributes = {}; - for (var i = 0; i < elem.attributes.length; ++i) { - var attr = elem.attributes[i]; + const attributes = {}; + for (let i = 0; i < elem.attributes.length; ++i) { + const attr = elem.attributes[i]; attributes[attr.name] = attr.value; } out.attributes = attributes; - var client_rects = elem.getClientRects(); - for (var k = 0; k < client_rects.length; ++k) { - var rect = client_rects[k]; + const client_rects = elem.getClientRects(); + for (let k = 0; k < client_rects.length; ++k) { + const rect = client_rects[k]; out.rects.push({ "top": rect.top, "right": rect.right, @@ -111,8 +111,8 @@ window._qutebrowser.webelem = (function() { // the cVim implementation here? // https://github.com/1995eaton/chromium-vim/blob/1.2.85/content_scripts/dom.js#L74-L134 - var win = elem.ownerDocument.defaultView; - var rect = elem.getBoundingClientRect(); + const win = elem.ownerDocument.defaultView; + let rect = elem.getBoundingClientRect(); if (!rect || rect.top > window.innerHeight || @@ -127,7 +127,7 @@ window._qutebrowser.webelem = (function() { return false; } - var style = win.getComputedStyle(elem, null); + const style = win.getComputedStyle(elem, null); if (style.getPropertyValue("visibility") !== "visible" || style.getPropertyValue("display") === "none" || style.getPropertyValue("opacity") === "0") { @@ -144,11 +144,11 @@ window._qutebrowser.webelem = (function() { return true; } - funcs.find_css = function(selector, only_visible) { - var elems = document.querySelectorAll(selector); - var out = []; + funcs.find_css = (selector, only_visible) => { + const elems = document.querySelectorAll(selector); + const out = []; - for (var i = 0; i < elems.length; ++i) { + for (let i = 0; i < elems.length; ++i) { if (!only_visible || is_visible(elems[i])) { out.push(serialize_elem(elems[i])); } @@ -157,13 +157,13 @@ window._qutebrowser.webelem = (function() { return out; }; - funcs.find_id = function(id) { - var elem = document.getElementById(id); + funcs.find_id = (id) => { + const elem = document.getElementById(id); return serialize_elem(elem); }; - funcs.find_focused = function() { - var elem = document.activeElement; + funcs.find_focused = () => { + const elem = document.activeElement; if (!elem || elem === document.body) { // "When there is no selection, the active element is the page's @@ -174,43 +174,43 @@ window._qutebrowser.webelem = (function() { return serialize_elem(elem); }; - funcs.find_at_pos = function(x, y) { + funcs.find_at_pos = (x, y) => { // FIXME:qtwebengine // If the element at the specified point belongs to another document // (for example, an iframe's subdocument), the subdocument's parent // element is returned (the iframe itself). - var elem = document.elementFromPoint(x, y); + const elem = document.elementFromPoint(x, y); return serialize_elem(elem); }; // Function for returning a selection to python (so we can click it) - funcs.find_selected_link = function() { - var elem = window.getSelection().anchorNode; + funcs.find_selected_link = () => { + const elem = window.getSelection().anchorNode; if (!elem) { return null; } return serialize_elem(elem.parentNode); }; - funcs.set_value = function(id, value) { + funcs.set_value = (id, value) => { elements[id].value = value; }; - funcs.insert_text = function(id, text) { - var elem = elements[id]; + funcs.insert_text = (id, text) => { + const elem = elements[id]; elem.focus(); document.execCommand("insertText", false, text); }; - funcs.set_attribute = function(id, name, value) { + funcs.set_attribute = (id, name, value) => { elements[id].setAttribute(name, value); }; - funcs.remove_blank_target = function(id) { - var elem = elements[id]; + funcs.remove_blank_target = (id) => { + let elem = elements[id]; while (elem !== null) { - var tag = elem.tagName.toLowerCase(); + const tag = elem.tagName.toLowerCase(); if (tag === "a" || tag === "area") { if (elem.getAttribute("target") === "_blank") { elem.setAttribute("target", "_top"); @@ -221,18 +221,18 @@ window._qutebrowser.webelem = (function() { } }; - funcs.click = function(id) { - var elem = elements[id]; + funcs.click = (id) => { + const elem = elements[id]; elem.click(); }; - funcs.focus = function(id) { - var elem = elements[id]; + funcs.focus = (id) => { + const elem = elements[id]; elem.focus(); }; - funcs.move_cursor_to_end = function(id) { - var elem = elements[id]; + funcs.move_cursor_to_end = (id) => { + const elem = elements[id]; elem.selectionStart = elem.value.length; elem.selectionEnd = elem.value.length; }; From 905c9841484ded56cb66ab8d634c5f277797bcfe Mon Sep 17 00:00:00 2001 From: plexigras Date: Tue, 31 Oct 2017 12:54:26 +0100 Subject: [PATCH 68/89] change some lambdas to functions --- qutebrowser/javascript/history.js | 2 +- qutebrowser/javascript/position_caret.js | 2 +- qutebrowser/javascript/scroll.js | 2 +- qutebrowser/javascript/webelem.js | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/qutebrowser/javascript/history.js b/qutebrowser/javascript/history.js index ba25c8c00..417441bd9 100644 --- a/qutebrowser/javascript/history.js +++ b/qutebrowser/javascript/history.js @@ -19,7 +19,7 @@ "use strict"; -window.loadHistory = (() => { +window.loadHistory = (function() { // Date of last seen item. let lastItemDate = null; diff --git a/qutebrowser/javascript/position_caret.js b/qutebrowser/javascript/position_caret.js index 13203b781..0ead360fb 100644 --- a/qutebrowser/javascript/position_caret.js +++ b/qutebrowser/javascript/position_caret.js @@ -27,7 +27,7 @@ */ "use strict"; -(() => { +(function() { // FIXME:qtwebengine integrate this with other window._qutebrowser code? function isElementInViewport(node) { // eslint-disable-line complexity let i; diff --git a/qutebrowser/javascript/scroll.js b/qutebrowser/javascript/scroll.js index 97713367a..7205cb151 100644 --- a/qutebrowser/javascript/scroll.js +++ b/qutebrowser/javascript/scroll.js @@ -19,7 +19,7 @@ "use strict"; -window._qutebrowser.scroll = (() => { +window._qutebrowser.scroll = (function() { const funcs = {}; funcs.to_perc = (x, y) => { diff --git a/qutebrowser/javascript/webelem.js b/qutebrowser/javascript/webelem.js index 5f4a4b5e5..6820a17b3 100644 --- a/qutebrowser/javascript/webelem.js +++ b/qutebrowser/javascript/webelem.js @@ -36,7 +36,7 @@ "use strict"; -window._qutebrowser.webelem = (() => { +window._qutebrowser.webelem = (function() { const funcs = {}; const elements = []; From dc26808a941910ab41176f8f393de0b912241598 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 31 Oct 2017 14:41:36 +0100 Subject: [PATCH 69/89] Fix setting names in FAQ --- doc/faq.asciidoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/faq.asciidoc b/doc/faq.asciidoc index 75fe5ee8c..2091b9e0b 100644 --- a/doc/faq.asciidoc +++ b/doc/faq.asciidoc @@ -152,7 +152,7 @@ For QtWebEngine: `:set spellcheck.languages "['en-US', 'pl-PL']"` How do I use Tor with qutebrowser?:: - Start tor on your machine, and do `:set network proxy socks://localhost:9050/` + Start tor on your machine, and do `:set content.proxy socks://localhost:9050/` in qutebrowser. Note this won't give you the same amount of fingerprinting protection that the Tor Browser does, but it's useful to be able to access `.onion` sites. @@ -162,7 +162,7 @@ Why does J move to the next (right) tab, and K to the previous (left) one?:: and qutebrowser's keybindings are designed to be compatible with dwb's. The rationale behind it is that J is "down" in vim, and K is "up", which corresponds nicely to "next"/"previous". It also makes much more sense with - vertical tabs (e.g. `:set tabs position left`). + vertical tabs (e.g. `:set tabs.position left`). What's the difference between insert and passthrough mode?:: They are quite similar, but insert mode has some bindings (like `Ctrl-e` to From fa8476f76242c3e5cf7d14396607997c0956dbec Mon Sep 17 00:00:00 2001 From: plexigras Date: Tue, 31 Oct 2017 16:40:09 +0100 Subject: [PATCH 70/89] keep old pac_utils.js --- qutebrowser/javascript/pac_utils.js | 104 ++++++++++++++-------------- 1 file changed, 52 insertions(+), 52 deletions(-) diff --git a/qutebrowser/javascript/pac_utils.js b/qutebrowser/javascript/pac_utils.js index d77a5049d..0aba4c070 100644 --- a/qutebrowser/javascript/pac_utils.js +++ b/qutebrowser/javascript/pac_utils.js @@ -39,7 +39,7 @@ /* Script for Proxy Auto Config in the new world order. - - Gagan Saksena 04/24/00 + - Gagan Saksena 04/24/00 */ function dnsDomainIs(host, domain) { @@ -52,8 +52,8 @@ function dnsDomainLevels(host) { } function convert_addr(ipchars) { - const bytes = ipchars.split('.'); - const result = ((bytes[0] & 0xff) << 24) | + var bytes = ipchars.split('.'); + var result = ((bytes[0] & 0xff) << 24) | ((bytes[1] & 0xff) << 16) | ((bytes[2] & 0xff) << 8) | (bytes[3] & 0xff); @@ -61,7 +61,7 @@ function convert_addr(ipchars) { } function isInNet(ipaddr, pattern, maskstr) { - const test = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/ + var test = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/ .exec(ipaddr); if (test == null) { ipaddr = dnsResolve(ipaddr); @@ -71,9 +71,9 @@ function isInNet(ipaddr, pattern, maskstr) { test[3] > 255 || test[4] > 255) { return false; // not an IP address } - const host = convert_addr(ipaddr); - const pat = convert_addr(pattern); - const mask = convert_addr(maskstr); + var host = convert_addr(ipaddr); + var pat = convert_addr(pattern); + var mask = convert_addr(maskstr); return ((host & mask) == (pat & mask)); } @@ -82,75 +82,75 @@ function isPlainHostName(host) { } function isResolvable(host) { - const ip = dnsResolve(host); + var ip = dnsResolve(host); return (ip != null); } function localHostOrDomainIs(host, hostdom) { return (host == hostdom) || - (hostdom.lastIndexOf(`${host}.`, 0) == 0); + (hostdom.lastIndexOf(host + '.', 0) == 0); } function shExpMatch(url, pattern) { pattern = pattern.replace(/\./g, '\\.'); pattern = pattern.replace(/\*/g, '.*'); pattern = pattern.replace(/\?/g, '.'); - const newRe = new RegExp(`^${pattern}$`); + var newRe = new RegExp('^'+pattern+'$'); return newRe.test(url); } -const wdays = {SUN: 0, MON: 1, TUE: 2, WED: 3, THU: 4, FRI: 5, SAT: 6}; +var wdays = {SUN: 0, MON: 1, TUE: 2, WED: 3, THU: 4, FRI: 5, SAT: 6}; -const months = {JAN: 0, FEB: 1, MAR: 2, APR: 3, MAY: 4, JUN: 5, JUL: 6, +var months = {JAN: 0, FEB: 1, MAR: 2, APR: 3, MAY: 4, JUN: 5, JUL: 6, AUG: 7, SEP: 8, OCT: 9, NOV: 10, DEC: 11}; -function weekdayRange(...args) { +function weekdayRange() { function getDay(weekday) { if (weekday in wdays) { return wdays[weekday]; } return -1; } - const date = new Date(); - let argc = args.length; - let wday; + var date = new Date(); + var argc = arguments.length; + var wday; if (argc < 1) return false; - if (args[argc - 1] == 'GMT') { + if (arguments[argc - 1] == 'GMT') { argc--; wday = date.getUTCDay(); } else { wday = date.getDay(); } - const wd1 = getDay(args[0]); - const wd2 = (argc == 2) ? getDay(args[1]) : wd1; + var wd1 = getDay(arguments[0]); + var wd2 = (argc == 2) ? getDay(arguments[1]) : wd1; return (wd1 == -1 || wd2 == -1) ? false : (wd1 <= wday && wday <= wd2); } -function dateRange(...args) { +function dateRange() { function getMonth(name) { if (name in months) { return months[name]; } return -1; } - let date = new Date(); - let argc = args.length; + var date = new Date(); + var argc = arguments.length; if (argc < 1) { return false; } - const isGMT = (args[argc - 1] == 'GMT'); + var isGMT = (arguments[argc - 1] == 'GMT'); if (isGMT) { argc--; } // function will work even without explict handling of this case if (argc == 1) { - let tmp = parseInt(args[0]); + var tmp = parseInt(arguments[0]); if (isNaN(tmp)) { - return (isGMT ? date.getUTCMonth() : date.getMonth()) == - getMonth(args[0]); + return ((isGMT ? date.getUTCMonth() : date.getMonth()) == + getMonth(arguments[0])); } else if (tmp < 32) { return ((isGMT ? date.getUTCDate() : date.getDate()) == tmp); } else { @@ -158,15 +158,15 @@ function dateRange(...args) { tmp); } } - const year = date.getFullYear(); - let date1, date2; + var year = date.getFullYear(); + var date1, date2; date1 = new Date(year, 0, 1, 0, 0, 0); date2 = new Date(year, 11, 31, 23, 59, 59); - let adjustMonth = false; - for (let i = 0; i < (argc >> 1); i++) { - let tmp = parseInt(args[i]); + var adjustMonth = false; + for (var i = 0; i < (argc >> 1); i++) { + var tmp = parseInt(arguments[i]); if (isNaN(tmp)) { - let mon = getMonth(args[i]); + var mon = getMonth(arguments[i]); date1.setMonth(mon); } else if (tmp < 32) { adjustMonth = (argc <= 2); @@ -175,10 +175,10 @@ function dateRange(...args) { date1.setFullYear(tmp); } } - for (let i = (argc >> 1); i < argc; i++) { - let tmp = parseInt(args[i]); + for (var i = (argc >> 1); i < argc; i++) { + var tmp = parseInt(arguments[i]); if (isNaN(tmp)) { - let mon = getMonth(args[i]); + var mon = getMonth(arguments[i]); date2.setMonth(mon); } else if (tmp < 32) { date2.setDate(tmp); @@ -191,7 +191,7 @@ function dateRange(...args) { date2.setMonth(date.getMonth()); } if (isGMT) { - let tmp = date; + var tmp = date; tmp.setFullYear(date.getUTCFullYear()); tmp.setMonth(date.getUTCMonth()); tmp.setDate(date.getUTCDate()); @@ -203,39 +203,39 @@ function dateRange(...args) { return ((date1 <= date) && (date <= date2)); } -function timeRange(...args) { - let argc = args.length; - const date = new Date(); - let isGMT= false; +function timeRange() { + var argc = arguments.length; + var date = new Date(); + var isGMT= false; if (argc < 1) { return false; } - if (args[argc - 1] == 'GMT') { + if (arguments[argc - 1] == 'GMT') { isGMT = true; argc--; } - const hour = isGMT ? date.getUTCHours() : date.getHours(); - let date1, date2; + var hour = isGMT ? date.getUTCHours() : date.getHours(); + var date1, date2; date1 = new Date(); date2 = new Date(); if (argc == 1) { - return hour == args[0]; + return (hour == arguments[0]); } else if (argc == 2) { - return (args[0] <= hour) && (hour <= args[1]); + return ((arguments[0] <= hour) && (hour <= arguments[1])); } else { switch (argc) { case 6: - date1.setSeconds(args[2]); - date2.setSeconds(args[5]); + date1.setSeconds(arguments[2]); + date2.setSeconds(arguments[5]); case 4: - const middle = argc >> 1; - date1.setHours(args[0]); - date1.setMinutes(args[1]); - date2.setHours(args[middle]); - date2.setMinutes(args[middle + 1]); + var middle = argc >> 1; + date1.setHours(arguments[0]); + date1.setMinutes(arguments[1]); + date2.setHours(arguments[middle]); + date2.setMinutes(arguments[middle + 1]); if (middle == 2) { date2.setSeconds(59); } From dcc53026a39edc9eebb3c416f9ca0974669eb813 Mon Sep 17 00:00:00 2001 From: Luca Benci Date: Tue, 31 Oct 2017 23:14:07 +0100 Subject: [PATCH 71/89] Stay within 79 columns --- qutebrowser/completion/completer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qutebrowser/completion/completer.py b/qutebrowser/completion/completer.py index f0efd6751..bc0e4991f 100644 --- a/qutebrowser/completion/completer.py +++ b/qutebrowser/completion/completer.py @@ -183,7 +183,8 @@ class Completer(QObject): if maxsplit is not None and maxsplit < len(before): self._change_completed_part(text, before, after) else: - self._change_completed_part(text, before, after, immediate=True) + self._change_completed_part(text, before, after, + immediate=True) else: self._change_completed_part(text, before, after) From 35597a7c0152013f3306d1b1a8c56e95067e7477 Mon Sep 17 00:00:00 2001 From: Luca Benci Date: Tue, 31 Oct 2017 23:15:11 +0100 Subject: [PATCH 72/89] Change tests for trailing-space behaviour change --- tests/unit/completion/test_completer.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/unit/completion/test_completer.py b/tests/unit/completion/test_completer.py index 5f1581aa0..934872f2b 100644 --- a/tests/unit/completion/test_completer.py +++ b/tests/unit/completion/test_completer.py @@ -274,7 +274,11 @@ def test_on_selection_changed(before, newtxt, after, completer_obj, check(True, 2, after_txt, after_pos) # quick-completing a single item should move the cursor ahead by 1 and add - # a trailing space if at the end of the cmd string + # a trailing space if at the end of the cmd string, unless the command has + # maxsplit < len(before) (such as :open in these tests) + if after_txt.startswith(':open'): + return + after_pos += 1 if after_pos > len(after_txt): after_txt += ' ' From 370405c0ed5fb15dec7f59bca31b35657ee363dd Mon Sep 17 00:00:00 2001 From: Luca Benci Date: Tue, 31 Oct 2017 23:20:13 +0100 Subject: [PATCH 73/89] Remove assert --- qutebrowser/browser/webkit/webkitelem.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qutebrowser/browser/webkit/webkitelem.py b/qutebrowser/browser/webkit/webkitelem.py index 86c701234..2a1eafc9e 100644 --- a/qutebrowser/browser/webkit/webkitelem.py +++ b/qutebrowser/browser/webkit/webkitelem.py @@ -130,7 +130,6 @@ class WebKitElement(webelem.AbstractWebElement): """Get the text caret position for the current element.""" self._check_vanished() pos = self._elem.evaluateJavaScript('this.selectionStart') - assert isinstance(pos, (int, float, type(None))) if pos is None: return 0 return int(pos) From bc0c877b8729d3f155292881f0171e77f2f0441d Mon Sep 17 00:00:00 2001 From: Luca Benci Date: Tue, 31 Oct 2017 23:21:37 +0100 Subject: [PATCH 74/89] Formatting --- qutebrowser/javascript/webelem.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/qutebrowser/javascript/webelem.js b/qutebrowser/javascript/webelem.js index 0a23ad543..cbd1b16d8 100644 --- a/qutebrowser/javascript/webelem.js +++ b/qutebrowser/javascript/webelem.js @@ -53,10 +53,8 @@ window._qutebrowser.webelem = (function() { try { caret_position = elem.selectionStart; } catch (err) { - if ( - err instanceof DOMException && - err.name === "InvalidStateError" - ) { + if (err instanceof DOMException && + err.name === "InvalidStateError") { // nothing to do, caret_position is already 0 } else { // not the droid we're looking for From 24231ae4059e272dfeb42d4ccdc55cbaa8704ad7 Mon Sep 17 00:00:00 2001 From: Luca Benci Date: Tue, 31 Oct 2017 23:22:17 +0100 Subject: [PATCH 75/89] Remove unnecessary parens --- qutebrowser/misc/editor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/misc/editor.py b/qutebrowser/misc/editor.py index 2fd1d8597..2be96e012 100644 --- a/qutebrowser/misc/editor.py +++ b/qutebrowser/misc/editor.py @@ -181,7 +181,7 @@ class ExternalEditor(QObject): """ line = text[:caret_position].count('\n') + 1 column = caret_position - text[:caret_position].rfind('\n') - return (line, column) + return line, column def _sub_placeholder(self, arg, line, column): """Substitute a single placeholder. From cb7e6ab02d22995bff0728ee5ee78820221f2d2e Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Tue, 31 Oct 2017 19:06:39 -0400 Subject: [PATCH 76/89] Abort pinned tab prompt if tab is destroyed Closes #3223 --- qutebrowser/mainwindow/tabbedbrowser.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index 48803f1c8..5c3b10148 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -259,13 +259,14 @@ class TabbedBrowser(tabwidget.TabWidget): def tab_close_prompt_if_pinned(self, tab, force, yes_action): """Helper method for tab_close. - If tab is pinned, prompt. If everything is good, run yes_action. + If tab is pinned, prompt. If not, run yes_action. + If tab is destroyed, abort question. """ if tab.data.pinned and not force: message.confirm_async( title='Pinned Tab', text="Are you sure you want to close a pinned tab?", - yes_action=yes_action, default=False) + yes_action=yes_action, default=False, abort_on=[tab.destroyed]) else: yes_action() From 385337eb90966706960c618b75c9e6854fea1bcf Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 1 Nov 2017 09:24:57 +0100 Subject: [PATCH 77/89] Use lts version of NopeJS Looks like npm doesn't work with Node v9: https://github.com/nodejs/node/issues/16649 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 18d1dd416..65d917d73 100644 --- a/.travis.yml +++ b/.travis.yml @@ -51,7 +51,7 @@ matrix: env: TESTENV=eslint language: node_js python: null - node_js: node + node_js: "lts/*" fast_finish: true cache: From bb54a954fe58c45d4fb8e2a25833ecc1d0f422f2 Mon Sep 17 00:00:00 2001 From: Gyorgy Orban Date: Mon, 23 Oct 2017 11:22:56 +0200 Subject: [PATCH 78/89] use subprocess run The usage of subprocess.run is recommended since python 3.5. Popen, check_call, call and check_output calls were replaced with run. --- qutebrowser/app.py | 4 +- qutebrowser/utils/version.py | 10 +++-- scripts/asciidoc2html.py | 10 ++--- scripts/dev/build_release.py | 39 ++++++++++--------- scripts/dev/check_coverage.py | 8 ++-- scripts/dev/check_doc_changes.py | 5 ++- scripts/dev/gen_resources.py | 2 +- scripts/dev/get_coredumpctl_traces.py | 20 +++++----- scripts/dev/misc_checks.py | 3 +- scripts/dev/recompile_requirements.py | 8 ++-- scripts/dev/run_profile.py | 11 +++--- scripts/dev/run_pylint_on_tests.py | 2 +- scripts/dev/segfault_test.py | 10 ++--- scripts/dev/src2asciidoc.py | 6 +-- scripts/link_pyqt.py | 6 ++- scripts/setupcommon.py | 10 +++-- tests/end2end/features/test_qutescheme_bdd.py | 6 +-- tests/end2end/test_invocations.py | 7 ++-- tests/unit/misc/test_checkpyver.py | 7 ++-- tests/unit/utils/test_standarddir.py | 5 ++- tests/unit/utils/test_version.py | 17 ++++---- 21 files changed, 105 insertions(+), 91 deletions(-) diff --git a/qutebrowser/app.py b/qutebrowser/app.py index 2ed579f61..02eb9bf18 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -660,9 +660,9 @@ class Quitter: try: args, cwd = self._get_restart_args(pages, session, override_args) if cwd is None: - subprocess.Popen(args) + subprocess.run(args) else: - subprocess.Popen(args, cwd=cwd) + subprocess.run(args, cwd=cwd) except OSError: log.destroy.exception("Failed to restart") return False diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py index bc04c7524..547185623 100644 --- a/qutebrowser/utils/version.py +++ b/qutebrowser/utils/version.py @@ -151,12 +151,14 @@ def _git_str_subprocess(gitpath): return None try: # https://stackoverflow.com/questions/21017300/21017394#21017394 - commit_hash = subprocess.check_output( + commit_hash = subprocess.run( ['git', 'describe', '--match=NeVeRmAtCh', '--always', '--dirty'], - cwd=gitpath).decode('UTF-8').strip() - date = subprocess.check_output( + cwd=gitpath, check=True, + stdout=subprocess.PIPE).stdout.decode('UTF-8').strip() + date = subprocess.run( ['git', 'show', '-s', '--format=%ci', 'HEAD'], - cwd=gitpath).decode('UTF-8').strip() + cwd=gitpath, check=True, + stdout=subprocess.PIPE).stdout.decode('UTF-8').strip() return '{} ({})'.format(commit_hash, date) except (subprocess.CalledProcessError, OSError): return None diff --git a/scripts/asciidoc2html.py b/scripts/asciidoc2html.py index 51deb009f..4e9c7544b 100755 --- a/scripts/asciidoc2html.py +++ b/scripts/asciidoc2html.py @@ -224,16 +224,16 @@ class AsciiDoc: return self._args.asciidoc try: - subprocess.call(['asciidoc'], stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL) + subprocess.run(['asciidoc'], stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL) except OSError: pass else: return ['asciidoc'] try: - subprocess.call(['asciidoc.py'], stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL) + subprocess.run(['asciidoc.py'], stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL) except OSError: pass else: @@ -258,7 +258,7 @@ class AsciiDoc: try: env = os.environ.copy() env['HOME'] = self._homedir - subprocess.check_call(cmdline, env=env) + subprocess.run(cmdline, check=True, env=env) except (subprocess.CalledProcessError, OSError) as e: self._failed = True utils.print_col(str(e), 'red') diff --git a/scripts/dev/build_release.py b/scripts/dev/build_release.py index 0dc45f2e2..65d1d651b 100755 --- a/scripts/dev/build_release.py +++ b/scripts/dev/build_release.py @@ -50,7 +50,7 @@ def call_script(name, *args, python=sys.executable): python: The python interpreter to use. """ path = os.path.join(os.path.dirname(__file__), os.pardir, name) - subprocess.check_call([python, path] + list(args)) + subprocess.run([python, path] + list(args), check=True) def call_tox(toxenv, *args, python=sys.executable): @@ -64,9 +64,9 @@ def call_tox(toxenv, *args, python=sys.executable): env = os.environ.copy() env['PYTHON'] = python env['PATH'] = os.environ['PATH'] + os.pathsep + os.path.dirname(python) - subprocess.check_call( + subprocess.run( [sys.executable, '-m', 'tox', '-vv', '-e', toxenv] + list(args), - env=env) + env=env, check=True) def run_asciidoc2html(args): @@ -89,8 +89,9 @@ def _maybe_remove(path): def smoke_test(executable): """Try starting the given qutebrowser executable.""" - subprocess.check_call([executable, '--no-err-windows', '--nowindow', - '--temp-basedir', 'about:blank', ':later 500 quit']) + subprocess.run([executable, '--no-err-windows', '--nowindow', + '--temp-basedir', 'about:blank', ':later 500 quit'], + check=True) def patch_mac_app(): @@ -178,7 +179,7 @@ def build_mac(): utils.print_title("Patching .app") patch_mac_app() utils.print_title("Building .dmg") - subprocess.check_call(['make', '-f', 'scripts/dev/Makefile-dmg']) + subprocess.run(['make', '-f', 'scripts/dev/Makefile-dmg'], check=True) dmg_name = 'qutebrowser-{}.dmg'.format(qutebrowser.__version__) os.rename('qutebrowser.dmg', dmg_name) @@ -187,14 +188,14 @@ def build_mac(): try: with tempfile.TemporaryDirectory() as tmpdir: - subprocess.check_call(['hdiutil', 'attach', dmg_name, - '-mountpoint', tmpdir]) + subprocess.run(['hdiutil', 'attach', dmg_name, + '-mountpoint', tmpdir], check=True) try: binary = os.path.join(tmpdir, 'qutebrowser.app', 'Contents', 'MacOS', 'qutebrowser') smoke_test(binary) finally: - subprocess.call(['hdiutil', 'detach', tmpdir]) + subprocess.run(['hdiutil', 'detach', tmpdir]) except PermissionError as e: print("Failed to remove tempdir: {}".format(e)) @@ -242,13 +243,13 @@ def build_windows(): patch_windows(out_64) utils.print_title("Building installers") - subprocess.check_call(['makensis.exe', - '/DVERSION={}'.format(qutebrowser.__version__), - 'misc/qutebrowser.nsi']) - subprocess.check_call(['makensis.exe', - '/DX64', - '/DVERSION={}'.format(qutebrowser.__version__), - 'misc/qutebrowser.nsi']) + subprocess.run(['makensis.exe', + '/DVERSION={}'.format(qutebrowser.__version__), + 'misc/qutebrowser.nsi'], check=True) + subprocess.run(['makensis.exe', + '/DX64', + '/DVERSION={}'.format(qutebrowser.__version__), + 'misc/qutebrowser.nsi'], check=True) name_32 = 'qutebrowser-{}-win32.exe'.format(qutebrowser.__version__) name_64 = 'qutebrowser-{}-amd64.exe'.format(qutebrowser.__version__) @@ -292,12 +293,12 @@ def build_sdist(): _maybe_remove('dist') - subprocess.check_call([sys.executable, 'setup.py', 'sdist']) + subprocess.run([sys.executable, 'setup.py', 'sdist'], check=True) dist_files = os.listdir(os.path.abspath('dist')) assert len(dist_files) == 1 dist_file = os.path.join('dist', dist_files[0]) - subprocess.check_call(['gpg', '--detach-sign', '-a', dist_file]) + subprocess.run(['gpg', '--detach-sign', '-a', dist_file], check=True) tar = tarfile.open(dist_file) by_ext = collections.defaultdict(list) @@ -366,7 +367,7 @@ def github_upload(artifacts, tag): def pypi_upload(artifacts): """Upload the given artifacts to PyPI using twine.""" filenames = [a[0] for a in artifacts] - subprocess.check_call(['twine', 'upload'] + filenames) + subprocess.run(['twine', 'upload'] + filenames, check=True) def main(): diff --git a/scripts/dev/check_coverage.py b/scripts/dev/check_coverage.py index ba4e7a7a7..f2c9dd853 100644 --- a/scripts/dev/check_coverage.py +++ b/scripts/dev/check_coverage.py @@ -285,8 +285,8 @@ def main_check(): print(msg.text) print() filters = ','.join('qutebrowser/' + msg.filename for msg in messages) - subprocess.check_call([sys.executable, '-m', 'coverage', 'report', - '--show-missing', '--include', filters]) + subprocess.run([sys.executable, '-m', 'coverage', 'report', + '--show-missing', '--include', filters], check=True) print() print("To debug this, run 'tox -e py36-pyqt59-cov' " "(or py35-pyqt59-cov) locally and check htmlcov/index.html") @@ -312,9 +312,9 @@ def main_check_all(): for test_file, src_file in PERFECT_FILES: if test_file is None: continue - subprocess.check_call( + subprocess.run( [sys.executable, '-m', 'pytest', '--cov', 'qutebrowser', - '--cov-report', 'xml', test_file]) + '--cov-report', 'xml', test_file], check=True) with open('coverage.xml', encoding='utf-8') as f: messages = check(f, [(test_file, src_file)]) os.remove('coverage.xml') diff --git a/scripts/dev/check_doc_changes.py b/scripts/dev/check_doc_changes.py index 7fc993283..1cc2a90f9 100755 --- a/scripts/dev/check_doc_changes.py +++ b/scripts/dev/check_doc_changes.py @@ -24,7 +24,8 @@ import sys import subprocess import os -code = subprocess.call(['git', '--no-pager', 'diff', '--exit-code', '--stat']) +code = subprocess.run(['git', '--no-pager', 'diff', + '--exit-code', '--stat']).returncode if os.environ.get('TRAVIS_PULL_REQUEST', 'false') != 'false': if code != 0: @@ -42,6 +43,6 @@ if code != 0: if 'TRAVIS' in os.environ: print() print("travis_fold:start:gitdiff") - subprocess.call(['git', '--no-pager', 'diff']) + subprocess.run(['git', '--no-pager', 'diff']) print("travis_fold:end:gitdiff") sys.exit(code) diff --git a/scripts/dev/gen_resources.py b/scripts/dev/gen_resources.py index 12bf5b97c..cbfc69b6f 100644 --- a/scripts/dev/gen_resources.py +++ b/scripts/dev/gen_resources.py @@ -23,4 +23,4 @@ import subprocess with open('qutebrowser/resources.py', 'w', encoding='utf-8') as f: - subprocess.check_call(['pyrcc5', 'qutebrowser.rcc'], stdout=f) + subprocess.run(['pyrcc5', 'qutebrowser.rcc'], stdout=f, check=True) diff --git a/scripts/dev/get_coredumpctl_traces.py b/scripts/dev/get_coredumpctl_traces.py index 6d1a41a11..711bc52c0 100644 --- a/scripts/dev/get_coredumpctl_traces.py +++ b/scripts/dev/get_coredumpctl_traces.py @@ -84,7 +84,8 @@ def parse_coredumpctl_line(line): def get_info(pid): """Get and parse "coredumpctl info" output for the given PID.""" data = {} - output = subprocess.check_output(['coredumpctl', 'info', str(pid)]) + output = subprocess.run(['coredumpctl', 'info', str(pid)], check=True, + stdout=subprocess.PIPE).stdout output = output.decode('utf-8') for line in output.split('\n'): if not line.strip(): @@ -117,12 +118,12 @@ def dump_infos_gdb(parsed): """Dump all needed infos for the given crash using gdb.""" with tempfile.TemporaryDirectory() as tempdir: coredump = os.path.join(tempdir, 'dump') - subprocess.check_call(['coredumpctl', 'dump', '-o', coredump, - str(parsed.pid)]) - subprocess.check_call(['gdb', parsed.exe, coredump, - '-ex', 'info threads', - '-ex', 'thread apply all bt full', - '-ex', 'quit']) + subprocess.run(['coredumpctl', 'dump', '-o', coredump, + str(parsed.pid)], check=True) + subprocess.run(['gdb', parsed.exe, coredump, + '-ex', 'info threads', + '-ex', 'thread apply all bt full', + '-ex', 'quit'], check=True) def dump_infos(parsed): @@ -143,7 +144,7 @@ def check_prerequisites(): """Check if coredumpctl/gdb are installed.""" for binary in ['coredumpctl', 'gdb']: try: - subprocess.check_call([binary, '--version']) + subprocess.run([binary, '--version'], check=True) except FileNotFoundError: print("{} is needed to run this script!".format(binary), file=sys.stderr) @@ -158,7 +159,8 @@ def main(): action='store_true') args = parser.parse_args() - coredumps = subprocess.check_output(['coredumpctl', 'list']) + coredumps = subprocess.run(['coredumpctl', 'list'], check=True, + stdout=subprocess.PIPE).stdout lines = coredumps.decode('utf-8').split('\n') for line in lines[1:]: if not line.strip(): diff --git a/scripts/dev/misc_checks.py b/scripts/dev/misc_checks.py index 1bb263d15..e1517391d 100644 --- a/scripts/dev/misc_checks.py +++ b/scripts/dev/misc_checks.py @@ -62,7 +62,8 @@ def check_git(): print() return False untracked = [] - gitst = subprocess.check_output(['git', 'status', '--porcelain']) + gitst = subprocess.run(['git', 'status', '--porcelain'], check=True, + stdout=subprocess.PIPE).stdout gitst = gitst.decode('UTF-8').strip() for line in gitst.splitlines(): s, name = line.split(maxsplit=1) diff --git a/scripts/dev/recompile_requirements.py b/scripts/dev/recompile_requirements.py index ddc85dfec..4718ca7ab 100644 --- a/scripts/dev/recompile_requirements.py +++ b/scripts/dev/recompile_requirements.py @@ -116,9 +116,11 @@ def main(): with tempfile.TemporaryDirectory() as tmpdir: pip_bin = os.path.join(tmpdir, 'bin', 'pip') - subprocess.check_call(['virtualenv', tmpdir]) - subprocess.check_call([pip_bin, 'install', '-r', filename]) - reqs = subprocess.check_output([pip_bin, 'freeze']).decode('utf-8') + subprocess.run(['virtualenv', tmpdir], check=True) + subprocess.run([pip_bin, 'install', '-r', filename], check=True) + reqs = subprocess.run([pip_bin, 'freeze'], check=True, + stdout=subprocess.PIPE + ).stdout.decode('utf-8') with open(filename, 'r', encoding='utf-8') as f: comments = read_comments(f) diff --git a/scripts/dev/run_profile.py b/scripts/dev/run_profile.py index 31fe539aa..2247bd842 100755 --- a/scripts/dev/run_profile.py +++ b/scripts/dev/run_profile.py @@ -76,14 +76,15 @@ def main(): pass elif args.profile_tool == 'gprof2dot': # yep, shell=True. I know what I'm doing. - subprocess.call('gprof2dot -f pstats {} | dot -Tpng | feh -F -'.format( - shlex.quote(profilefile)), shell=True) + subprocess.run( + 'gprof2dot -f pstats {} | dot -Tpng | feh -F -'.format( + shlex.quote(profilefile)), shell=True) elif args.profile_tool == 'kcachegrind': callgraphfile = os.path.join(tempdir, 'callgraph') - subprocess.call(['pyprof2calltree', '-k', '-i', profilefile, - '-o', callgraphfile]) + subprocess.run(['pyprof2calltree', '-k', '-i', profilefile, + '-o', callgraphfile]) elif args.profile_tool == 'snakeviz': - subprocess.call(['snakeviz', profilefile]) + subprocess.run(['snakeviz', profilefile]) shutil.rmtree(tempdir) diff --git a/scripts/dev/run_pylint_on_tests.py b/scripts/dev/run_pylint_on_tests.py index e6263692b..1f99aa362 100644 --- a/scripts/dev/run_pylint_on_tests.py +++ b/scripts/dev/run_pylint_on_tests.py @@ -73,7 +73,7 @@ def main(): env = os.environ.copy() env['PYTHONPATH'] = os.pathsep.join(pythonpath) - ret = subprocess.call(['pylint'] + args, env=env) + ret = subprocess.run(['pylint'] + args, env=env).returncode return ret diff --git a/scripts/dev/segfault_test.py b/scripts/dev/segfault_test.py index c5e7c106f..642bb837d 100755 --- a/scripts/dev/segfault_test.py +++ b/scripts/dev/segfault_test.py @@ -92,22 +92,22 @@ def main(): utils.print_bold("==== {} ====".format(page)) if test_harfbuzz: print("With system harfbuzz:") - ret = subprocess.call([sys.executable, '-c', SCRIPT, page]) + ret = subprocess.run([sys.executable, '-c', SCRIPT, page]).returncode print_ret(ret) retvals.append(ret) if test_harfbuzz: print("With QT_HARFBUZZ=old:") env = dict(os.environ) env['QT_HARFBUZZ'] = 'old' - ret = subprocess.call([sys.executable, '-c', SCRIPT, page], - env=env) + ret = subprocess.run([sys.executable, '-c', SCRIPT, page], + env=env).returncode print_ret(ret) retvals.append(ret) print("With QT_HARFBUZZ=new:") env = dict(os.environ) env['QT_HARFBUZZ'] = 'new' - ret = subprocess.call([sys.executable, '-c', SCRIPT, page], - env=env) + ret = subprocess.run([sys.executable, '-c', SCRIPT, page], + env=env).returncode print_ret(ret) retvals.append(ret) if all(r == 0 for r in retvals): diff --git a/scripts/dev/src2asciidoc.py b/scripts/dev/src2asciidoc.py index a112b60d4..a58791297 100755 --- a/scripts/dev/src2asciidoc.py +++ b/scripts/dev/src2asciidoc.py @@ -529,9 +529,9 @@ def regenerate_cheatsheet(): ] for filename, x, y in files: - subprocess.check_call(['inkscape', '-e', filename, '-b', 'white', - '-w', str(x), '-h', str(y), - 'misc/cheatsheet.svg']) + subprocess.run(['inkscape', '-e', filename, '-b', 'white', + '-w', str(x), '-h', str(y), + 'misc/cheatsheet.svg'], check=True) def main(): diff --git a/scripts/link_pyqt.py b/scripts/link_pyqt.py index a7de598cd..57eeb9138 100644 --- a/scripts/link_pyqt.py +++ b/scripts/link_pyqt.py @@ -46,12 +46,14 @@ def run_py(executable, *code): f.write('\n'.join(code)) cmd = [executable, filename] try: - ret = subprocess.check_output(cmd, universal_newlines=True) + ret = subprocess.run(cmd, universal_newlines=True, check=True, + stdout=subprocess.PIPE).stdout finally: os.remove(filename) else: cmd = [executable, '-c', '\n'.join(code)] - ret = subprocess.check_output(cmd, universal_newlines=True) + ret = subprocess.run(cmd, universal_newlines=True, check=True, + stdout=subprocess.PIPE).stdout return ret.rstrip() diff --git a/scripts/setupcommon.py b/scripts/setupcommon.py index 1f85b225f..1832ead86 100644 --- a/scripts/setupcommon.py +++ b/scripts/setupcommon.py @@ -51,12 +51,14 @@ def _git_str(): return None try: # https://stackoverflow.com/questions/21017300/21017394#21017394 - commit_hash = subprocess.check_output( + commit_hash = subprocess.run( ['git', 'describe', '--match=NeVeRmAtCh', '--always', '--dirty'], - cwd=BASEDIR).decode('UTF-8').strip() - date = subprocess.check_output( + cwd=BASEDIR, check=True, + stdout=subprocess.PIPE).stdout.decode('UTF-8').strip() + date = subprocess.run( ['git', 'show', '-s', '--format=%ci', 'HEAD'], - cwd=BASEDIR).decode('UTF-8').strip() + cwd=BASEDIR, check=True, + stdout=subprocess.PIPE).stdout.decode('UTF-8').strip() return '{} ({})'.format(commit_hash, date) except (subprocess.CalledProcessError, OSError): return None diff --git a/tests/end2end/features/test_qutescheme_bdd.py b/tests/end2end/features/test_qutescheme_bdd.py index 14bdc5cb3..1269d10ed 100644 --- a/tests/end2end/features/test_qutescheme_bdd.py +++ b/tests/end2end/features/test_qutescheme_bdd.py @@ -47,10 +47,10 @@ def update_documentation(): return try: - subprocess.call(['asciidoc'], stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL) + subprocess.run(['asciidoc'], stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL) except OSError: pytest.skip("Docs outdated and asciidoc unavailable!") update_script = os.path.join(script_path, 'asciidoc2html.py') - subprocess.call([sys.executable, update_script]) + subprocess.run([sys.executable, update_script]) diff --git a/tests/end2end/test_invocations.py b/tests/end2end/test_invocations.py index 1a91be2e0..ef97d5317 100644 --- a/tests/end2end/test_invocations.py +++ b/tests/end2end/test_invocations.py @@ -259,14 +259,13 @@ def test_command_on_start(request, quteproc_new): def test_launching_with_python2(): try: - proc = subprocess.Popen(['python2', '-m', 'qutebrowser', - '--no-err-windows'], stderr=subprocess.PIPE) + proc = subprocess.run(['python2', '-m', 'qutebrowser', + '--no-err-windows'], stderr=subprocess.PIPE) except FileNotFoundError: pytest.skip("python2 not found") - _stdout, stderr = proc.communicate() assert proc.returncode == 1 error = "At least Python 3.5 is required to run qutebrowser" - assert stderr.decode('ascii').startswith(error) + assert proc.stderr.decode('ascii').startswith(error) def test_initial_private_browsing(request, quteproc_new): diff --git a/tests/unit/misc/test_checkpyver.py b/tests/unit/misc/test_checkpyver.py index c5fa83a48..6487eb263 100644 --- a/tests/unit/misc/test_checkpyver.py +++ b/tests/unit/misc/test_checkpyver.py @@ -36,15 +36,14 @@ TEXT = (r"At least Python 3.5 is required to run qutebrowser, but it's " def test_python2(): """Run checkpyver with python 2.""" try: - proc = subprocess.Popen( + proc = subprocess.run( ['python2', checkpyver.__file__, '--no-err-windows'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) - stdout, stderr = proc.communicate() except FileNotFoundError: pytest.skip("python2 not found") - assert not stdout - stderr = stderr.decode('utf-8') + assert not proc.stdout + stderr = proc.stderr.decode('utf-8') assert re.match(TEXT, stderr), stderr assert proc.returncode == 1 diff --git a/tests/unit/utils/test_standarddir.py b/tests/unit/utils/test_standarddir.py index c0f63d410..5a3f74a66 100644 --- a/tests/unit/utils/test_standarddir.py +++ b/tests/unit/utils/test_standarddir.py @@ -555,8 +555,9 @@ def test_no_qapplication(qapp, tmpdir): pyfile = tmpdir / 'sub.py' pyfile.write_text(textwrap.dedent(sub_code), encoding='ascii') - output = subprocess.check_output([sys.executable, str(pyfile)] + sys.path, - universal_newlines=True) + output = subprocess.run([sys.executable, str(pyfile)] + sys.path, + universal_newlines=True, + check=True, stdout=subprocess.PIPE).stdout sub_locations = json.loads(output) standarddir._init_dirs() diff --git a/tests/unit/utils/test_version.py b/tests/unit/utils/test_version.py index 9db7603e3..9e5f455ef 100644 --- a/tests/unit/utils/test_version.py +++ b/tests/unit/utils/test_version.py @@ -299,8 +299,8 @@ class TestGitStr: def _has_git(): """Check if git is installed.""" try: - subprocess.check_call(['git', '--version'], stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL) + subprocess.run(['git', '--version'], stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, check=True) except (OSError, subprocess.CalledProcessError): return False else: @@ -337,12 +337,13 @@ class TestGitStrSubprocess: # If we don't call this with shell=True it might fail under # some environments on Windows... # http://bugs.python.org/issue24493 - subprocess.check_call( + subprocess.run( 'git -C "{}" {}'.format(tmpdir, ' '.join(args)), - env=env, shell=True) + env=env, check=True, shell=True) else: - subprocess.check_call( - ['git', '-C', str(tmpdir)] + list(args), env=env) + subprocess.run( + ['git', '-C', str(tmpdir)] + list(args), + check=True, env=env) (tmpdir / 'file').write_text("Hello World!", encoding='utf-8') _git('init') @@ -368,14 +369,14 @@ class TestGitStrSubprocess: subprocess.CalledProcessError(1, 'foobar') ]) def test_exception(self, exc, mocker, tmpdir): - """Test with subprocess.check_output raising an exception. + """Test with subprocess.run raising an exception. Args: exc: The exception to raise. """ m = mocker.patch('qutebrowser.utils.version.os') m.path.isdir.return_value = True - mocker.patch('qutebrowser.utils.version.subprocess.check_output', + mocker.patch('qutebrowser.utils.version.subprocess.run', side_effect=exc) ret = version._git_str_subprocess(str(tmpdir)) assert ret is None From 1c39715267c9dd242a06fe5478b1e9cbe831c1f6 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 1 Nov 2017 22:36:59 +0100 Subject: [PATCH 79/89] Clarify qute://configdiff/old title --- qutebrowser/config/configdiff.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/config/configdiff.py b/qutebrowser/config/configdiff.py index 3f07daba1..020be2c8c 100644 --- a/qutebrowser/config/configdiff.py +++ b/qutebrowser/config/configdiff.py @@ -755,6 +755,6 @@ def get_diff(): lexer = pygments.lexers.DiffLexer() formatter = pygments.formatters.HtmlFormatter( full=True, linenos='table', - title='Config diff') + title='Diffing pre-1.0 default config with pre-1.0 modified config') # pylint: enable=no-member return pygments.highlight(conf_diff + key_diff, lexer, formatter) From 4498141c8b2159c50d785f508c0e4103c6403c14 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 2 Nov 2017 09:15:41 +0100 Subject: [PATCH 80/89] Update changelog --- doc/changelog.asciidoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index f4e31d5e8..e4ab9a4d9 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -76,6 +76,8 @@ Fixed ~~~~~ - Handle accessing a locked sqlite database gracefully +- Abort pinned tab dialogs properly when a tab is closed e.g. by closing a + window. v1.0.2 ------ From 4a1cdef88720bd05852e4d893dd9b6b641e80df5 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 2 Nov 2017 11:03:19 +0100 Subject: [PATCH 81/89] Fix indent --- scripts/dev/build_release.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/dev/build_release.py b/scripts/dev/build_release.py index 65d1d651b..e12ff5c93 100755 --- a/scripts/dev/build_release.py +++ b/scripts/dev/build_release.py @@ -90,7 +90,7 @@ def _maybe_remove(path): def smoke_test(executable): """Try starting the given qutebrowser executable.""" subprocess.run([executable, '--no-err-windows', '--nowindow', - '--temp-basedir', 'about:blank', ':later 500 quit'], + '--temp-basedir', 'about:blank', ':later 500 quit'], check=True) From c0eae5d4e48dbfe620677fbcfefb34fb2326dc1b Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 2 Nov 2017 11:35:40 +0100 Subject: [PATCH 82/89] Update changelog --- doc/changelog.asciidoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index e4ab9a4d9..36079535c 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -34,6 +34,8 @@ Added widget. - `:edit-url` now handles the `--private` and `--related` flags, which have the same effect they have with `:open`. +- New `{line}` and `{column}` replacements for `editor.command` to position the + cursor correctly. Changed ~~~~~~~ From 98c6b49cdeabf0fabecbe4eb3195cc855bc123f0 Mon Sep 17 00:00:00 2001 From: Gyorgy Orban Date: Mon, 23 Oct 2017 11:42:28 +0200 Subject: [PATCH 83/89] use enum module instead or usertypes.enum Remove the usertypes.enum from the source and use the standard enum module instead. Enum start number is available since python 3.5 --- qutebrowser/browser/browsertab.py | 3 +- qutebrowser/browser/downloads.py | 4 +- qutebrowser/browser/hints.py | 8 +-- qutebrowser/browser/webelem.py | 3 +- qutebrowser/keyinput/basekeyparser.py | 5 +- qutebrowser/keyinput/modeparsers.py | 3 +- qutebrowser/mainwindow/statusbar/bar.py | 3 +- qutebrowser/mainwindow/statusbar/text.py | 6 +- qutebrowser/mainwindow/statusbar/url.py | 6 +- qutebrowser/mainwindow/tabwidget.py | 5 +- qutebrowser/misc/backendproblem.py | 5 +- qutebrowser/misc/crashdialog.py | 7 ++- qutebrowser/utils/docutils.py | 7 ++- qutebrowser/utils/standarddir.py | 9 +-- qutebrowser/utils/usertypes.py | 51 ++++++---------- qutebrowser/utils/version.py | 3 +- tests/unit/commands/test_argparser.py | 4 +- tests/unit/commands/test_cmdutils.py | 3 +- tests/unit/utils/usertypes/test_enum.py | 74 ------------------------ 19 files changed, 68 insertions(+), 141 deletions(-) delete mode 100644 tests/unit/utils/usertypes/test_enum.py diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index 141555290..547e276db 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -19,6 +19,7 @@ """Base class for a wrapper over QWebView/QWebEngineView.""" +import enum import itertools import attr @@ -74,7 +75,7 @@ class UnsupportedOperationError(WebTabError): """Raised when an operation is not supported with the given backend.""" -TerminationStatus = usertypes.enum('TerminationStatus', [ +TerminationStatus = enum.Enum('TerminationStatus', [ 'normal', 'abnormal', # non-zero exit status 'crashed', # e.g. segfault diff --git a/qutebrowser/browser/downloads.py b/qutebrowser/browser/downloads.py index 07e5c7c30..4ab271423 100644 --- a/qutebrowser/browser/downloads.py +++ b/qutebrowser/browser/downloads.py @@ -27,6 +27,7 @@ import collections import functools import pathlib import tempfile +import enum import sip from PyQt5.QtCore import (pyqtSlot, pyqtSignal, Qt, QObject, QModelIndex, @@ -38,8 +39,7 @@ from qutebrowser.utils import (usertypes, standarddir, utils, message, log, qtutils) -ModelRole = usertypes.enum('ModelRole', ['item'], start=Qt.UserRole, - is_int=True) +ModelRole = enum.IntEnum('ModelRole', ['item'], start=Qt.UserRole) # Remember the last used directory diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index 90ef9b7ec..4312a7876 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -24,6 +24,7 @@ import functools import math import re import html +import enum from string import ascii_lowercase import attr @@ -37,10 +38,9 @@ from qutebrowser.commands import userscripts, cmdexc, cmdutils, runners from qutebrowser.utils import usertypes, log, qtutils, message, objreg, utils -Target = usertypes.enum('Target', ['normal', 'current', 'tab', 'tab_fg', - 'tab_bg', 'window', 'yank', 'yank_primary', - 'run', 'fill', 'hover', 'download', - 'userscript', 'spawn']) +Target = enum.Enum('Target', ['normal', 'current', 'tab', 'tab_fg', 'tab_bg', + 'window', 'yank', 'yank_primary', 'run', 'fill', + 'hover', 'download', 'userscript', 'spawn']) class HintingError(Exception): diff --git a/qutebrowser/browser/webelem.py b/qutebrowser/browser/webelem.py index 85efebd03..067f33cff 100644 --- a/qutebrowser/browser/webelem.py +++ b/qutebrowser/browser/webelem.py @@ -24,6 +24,7 @@ Module attributes: SELECTORS: CSS selectors for different groups of elements. """ +import enum import collections.abc from PyQt5.QtCore import QUrl, Qt, QEvent, QTimer @@ -35,7 +36,7 @@ from qutebrowser.mainwindow import mainwindow from qutebrowser.utils import log, usertypes, utils, qtutils, objreg -Group = usertypes.enum('Group', ['all', 'links', 'images', 'url', 'inputs']) +Group = enum.Enum('Group', ['all', 'links', 'images', 'url', 'inputs']) SELECTORS = { diff --git a/qutebrowser/keyinput/basekeyparser.py b/qutebrowser/keyinput/basekeyparser.py index 2f75cbdfe..14efd00ab 100644 --- a/qutebrowser/keyinput/basekeyparser.py +++ b/qutebrowser/keyinput/basekeyparser.py @@ -19,6 +19,7 @@ """Base class for vim-like key sequence parser.""" +import enum import re import unicodedata @@ -75,8 +76,8 @@ class BaseKeyParser(QObject): do_log = True passthrough = False - Match = usertypes.enum('Match', ['partial', 'definitive', 'other', 'none']) - Type = usertypes.enum('Type', ['chain', 'special']) + Match = enum.Enum('Match', ['partial', 'definitive', 'other', 'none']) + Type = enum.Enum('Type', ['chain', 'special']) def __init__(self, win_id, parent=None, supports_count=None, supports_chains=False): diff --git a/qutebrowser/keyinput/modeparsers.py b/qutebrowser/keyinput/modeparsers.py index 7aa8ca371..40624cee6 100644 --- a/qutebrowser/keyinput/modeparsers.py +++ b/qutebrowser/keyinput/modeparsers.py @@ -24,6 +24,7 @@ Module attributes: """ import traceback +import enum from PyQt5.QtCore import pyqtSlot, Qt @@ -34,7 +35,7 @@ from qutebrowser.utils import usertypes, log, message, objreg, utils STARTCHARS = ":/?" -LastPress = usertypes.enum('LastPress', ['none', 'filtertext', 'keystring']) +LastPress = enum.Enum('LastPress', ['none', 'filtertext', 'keystring']) class NormalKeyParser(keyparser.CommandKeyParser): diff --git a/qutebrowser/mainwindow/statusbar/bar.py b/qutebrowser/mainwindow/statusbar/bar.py index da18deae8..61bb35ace 100644 --- a/qutebrowser/mainwindow/statusbar/bar.py +++ b/qutebrowser/mainwindow/statusbar/bar.py @@ -19,6 +19,7 @@ """The main statusbar widget.""" +import enum import attr from PyQt5.QtCore import pyqtSignal, pyqtSlot, pyqtProperty, Qt, QSize, QTimer from PyQt5.QtWidgets import QWidget, QHBoxLayout, QStackedLayout, QSizePolicy @@ -46,7 +47,7 @@ class ColorFlags: passthrough: If we're currently in passthrough-mode. """ - CaretMode = usertypes.enum('CaretMode', ['off', 'on', 'selection']) + CaretMode = enum.Enum('CaretMode', ['off', 'on', 'selection']) prompt = attr.ib(False) insert = attr.ib(False) command = attr.ib(False) diff --git a/qutebrowser/mainwindow/statusbar/text.py b/qutebrowser/mainwindow/statusbar/text.py index e99891ecd..b232cedc8 100644 --- a/qutebrowser/mainwindow/statusbar/text.py +++ b/qutebrowser/mainwindow/statusbar/text.py @@ -19,10 +19,12 @@ """Text displayed in the statusbar.""" +import enum + from PyQt5.QtCore import pyqtSlot from qutebrowser.mainwindow.statusbar import textbase -from qutebrowser.utils import usertypes, log +from qutebrowser.utils import log class Text(textbase.TextBase): @@ -37,7 +39,7 @@ class Text(textbase.TextBase): available. If not, the permanent text is shown. """ - Text = usertypes.enum('Text', ['normal', 'temp']) + Text = enum.Enum('Text', ['normal', 'temp']) def __init__(self, parent=None): super().__init__(parent) diff --git a/qutebrowser/mainwindow/statusbar/url.py b/qutebrowser/mainwindow/statusbar/url.py index f75960f88..a91c67550 100644 --- a/qutebrowser/mainwindow/statusbar/url.py +++ b/qutebrowser/mainwindow/statusbar/url.py @@ -19,6 +19,8 @@ """URL displayed in the statusbar.""" +import enum + from PyQt5.QtCore import pyqtSlot, pyqtProperty, Qt, QUrl from qutebrowser.mainwindow.statusbar import textbase @@ -27,8 +29,8 @@ from qutebrowser.utils import usertypes, urlutils # Note this has entries for success/error/warn from widgets.webview:LoadStatus -UrlType = usertypes.enum('UrlType', ['success', 'success_https', 'error', - 'warn', 'hover', 'normal']) +UrlType = enum.Enum('UrlType', ['success', 'success_https', 'error', 'warn', + 'hover', 'normal']) class UrlText(textbase.TextBase): diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py index 98d6bdbc7..5af709c85 100644 --- a/qutebrowser/mainwindow/tabwidget.py +++ b/qutebrowser/mainwindow/tabwidget.py @@ -20,6 +20,7 @@ """The tab widget used for TabbedBrowser from browser.py.""" import functools +import enum import attr from PyQt5.QtCore import (pyqtSignal, pyqtSlot, Qt, QSize, QRect, QPoint, @@ -34,8 +35,8 @@ from qutebrowser.config import config from qutebrowser.misc import objects -PixelMetrics = usertypes.enum('PixelMetrics', ['icon_padding'], - start=QStyle.PM_CustomBase, is_int=True) +PixelMetrics = enum.IntEnum('PixelMetrics', ['icon_padding'], + start=QStyle.PM_CustomBase) class TabWidget(QTabWidget): diff --git a/qutebrowser/misc/backendproblem.py b/qutebrowser/misc/backendproblem.py index f126ceda5..6adc0c722 100644 --- a/qutebrowser/misc/backendproblem.py +++ b/qutebrowser/misc/backendproblem.py @@ -25,6 +25,7 @@ import functools import html import ctypes import ctypes.util +import enum import attr from PyQt5.QtCore import Qt @@ -37,10 +38,10 @@ from qutebrowser.utils import usertypes, objreg, version, qtutils, log, utils from qutebrowser.misc import objects, msgbox -_Result = usertypes.enum( +_Result = enum.IntEnum( '_Result', ['quit', 'restart', 'restart_webkit', 'restart_webengine'], - is_int=True, start=QDialog.Accepted + 1) + start=QDialog.Accepted + 1) @attr.s diff --git a/qutebrowser/misc/crashdialog.py b/qutebrowser/misc/crashdialog.py index 2da8eeeaf..f8b37c742 100644 --- a/qutebrowser/misc/crashdialog.py +++ b/qutebrowser/misc/crashdialog.py @@ -27,6 +27,7 @@ import getpass import fnmatch import traceback import datetime +import enum import pkg_resources from PyQt5.QtCore import pyqtSlot, Qt, QSize @@ -35,14 +36,14 @@ from PyQt5.QtWidgets import (QDialog, QLabel, QTextEdit, QPushButton, QDialogButtonBox, QApplication, QMessageBox) import qutebrowser -from qutebrowser.utils import version, log, utils, objreg, usertypes +from qutebrowser.utils import version, log, utils, objreg from qutebrowser.misc import (miscwidgets, autoupdate, msgbox, httpclient, pastebin) from qutebrowser.config import config, configfiles -Result = usertypes.enum('Result', ['restore', 'no_restore'], is_int=True, - start=QDialog.Accepted + 1) +Result = enum.IntEnum('Result', ['restore', 'no_restore'], + start=QDialog.Accepted + 1) def parse_fatal_stacktrace(text): diff --git a/qutebrowser/utils/docutils.py b/qutebrowser/utils/docutils.py index 567451e05..d645b41ac 100644 --- a/qutebrowser/utils/docutils.py +++ b/qutebrowser/utils/docutils.py @@ -24,9 +24,10 @@ import sys import inspect import os.path import collections +import enum import qutebrowser -from qutebrowser.utils import usertypes, log, utils +from qutebrowser.utils import log, utils def is_git_repo(): @@ -75,8 +76,8 @@ class DocstringParser: arg_descs: A dict of argument names to their descriptions """ - State = usertypes.enum('State', ['short', 'desc', 'desc_hidden', - 'arg_start', 'arg_inside', 'misc']) + State = enum.Enum('State', ['short', 'desc', 'desc_hidden', + 'arg_start', 'arg_inside', 'misc']) def __init__(self, func): """Constructor. diff --git a/qutebrowser/utils/standarddir.py b/qutebrowser/utils/standarddir.py index c5ee356c0..1efd47590 100644 --- a/qutebrowser/utils/standarddir.py +++ b/qutebrowser/utils/standarddir.py @@ -24,19 +24,20 @@ import sys import shutil import os.path import contextlib +import enum from PyQt5.QtCore import QStandardPaths from PyQt5.QtWidgets import QApplication -from qutebrowser.utils import log, debug, usertypes, message, utils +from qutebrowser.utils import log, debug, message, utils # The cached locations _locations = {} -Location = usertypes.enum('Location', ['config', 'auto_config', - 'data', 'system_data', - 'cache', 'download', 'runtime']) +Location = enum.Enum('Location', ['config', 'auto_config', + 'data', 'system_data', + 'cache', 'download', 'runtime']) APPNAME = 'qutebrowser' diff --git a/qutebrowser/utils/usertypes.py b/qutebrowser/utils/usertypes.py index 1bc928697..99716c062 100644 --- a/qutebrowser/utils/usertypes.py +++ b/qutebrowser/utils/usertypes.py @@ -25,7 +25,7 @@ Module attributes: import operator import collections.abc -import enum as pyenum +import enum from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QTimer @@ -35,22 +35,6 @@ from qutebrowser.utils import log, qtutils, utils _UNSET = object() -def enum(name, items, start=1, is_int=False): - """Factory for simple enumerations. - - Args: - name: Name of the enum - items: Iterable of items to be sequentially enumerated. - start: The number to use for the first value. - We use 1 as default so enum members are always True. - is_init: True if the enum should be a Python IntEnum - """ - enums = [(v, i) for (i, v) in enumerate(items, start)] - base = pyenum.IntEnum if is_int else pyenum.Enum - base = pyenum.unique(base) - return base(name, enums) - - class NeighborList(collections.abc.Sequence): """A list of items which saves its current position. @@ -65,7 +49,7 @@ class NeighborList(collections.abc.Sequence): _mode: The current mode. """ - Modes = enum('Modes', ['edge', 'exception']) + Modes = enum.Enum('Modes', ['edge', 'exception']) def __init__(self, items=None, default=_UNSET, mode=Modes.exception): """Constructor. @@ -221,45 +205,46 @@ class NeighborList(collections.abc.Sequence): # The mode of a Question. -PromptMode = enum('PromptMode', ['yesno', 'text', 'user_pwd', 'alert', - 'download']) +PromptMode = enum.Enum('PromptMode', ['yesno', 'text', 'user_pwd', 'alert', + 'download']) # Where to open a clicked link. -ClickTarget = enum('ClickTarget', ['normal', 'tab', 'tab_bg', 'window', - 'hover']) +ClickTarget = enum.Enum('ClickTarget', ['normal', 'tab', 'tab_bg', 'window', + 'hover']) # Key input modes -KeyMode = enum('KeyMode', ['normal', 'hint', 'command', 'yesno', 'prompt', - 'insert', 'passthrough', 'caret', 'set_mark', - 'jump_mark', 'record_macro', 'run_macro']) +KeyMode = enum.Enum('KeyMode', ['normal', 'hint', 'command', 'yesno', 'prompt', + 'insert', 'passthrough', 'caret', 'set_mark', + 'jump_mark', 'record_macro', 'run_macro']) # Exit statuses for errors. Needs to be an int for sys.exit. -Exit = enum('Exit', ['ok', 'reserved', 'exception', 'err_ipc', 'err_init', - 'err_config', 'err_key_config'], is_int=True, start=0) +Exit = enum.IntEnum('Exit', ['ok', 'reserved', 'exception', 'err_ipc', + 'err_init', 'err_config', 'err_key_config'], + start=0) # Load status of a tab -LoadStatus = enum('LoadStatus', ['none', 'success', 'success_https', 'error', - 'warn', 'loading']) +LoadStatus = enum.Enum('LoadStatus', ['none', 'success', 'success_https', + 'error', 'warn', 'loading']) # Backend of a tab -Backend = enum('Backend', ['QtWebKit', 'QtWebEngine']) +Backend = enum.Enum('Backend', ['QtWebKit', 'QtWebEngine']) # JS world for QtWebEngine -JsWorld = enum('JsWorld', ['main', 'application', 'user', 'jseval']) +JsWorld = enum.Enum('JsWorld', ['main', 'application', 'user', 'jseval']) # Log level of a JS message. This needs to match up with the keys allowed for # the content.javascript.log setting. -JsLogLevel = enum('JsLogLevel', ['unknown', 'info', 'warning', 'error']) +JsLogLevel = enum.Enum('JsLogLevel', ['unknown', 'info', 'warning', 'error']) -MessageLevel = enum('MessageLevel', ['error', 'warning', 'info']) +MessageLevel = enum.Enum('MessageLevel', ['error', 'warning', 'info']) class Question(QObject): diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py index 547185623..9d1b7c90d 100644 --- a/qutebrowser/utils/version.py +++ b/qutebrowser/utils/version.py @@ -27,6 +27,7 @@ import platform import subprocess import importlib import collections +import enum import pkg_resources import attr @@ -63,7 +64,7 @@ class DistributionInfo: pretty = attr.ib() -Distribution = usertypes.enum( +Distribution = enum.Enum( 'Distribution', ['unknown', 'ubuntu', 'debian', 'void', 'arch', 'gentoo', 'fedora', 'opensuse', 'linuxmint', 'manjaro']) diff --git a/tests/unit/commands/test_argparser.py b/tests/unit/commands/test_argparser.py index 4cd3e7ee1..00f000acb 100644 --- a/tests/unit/commands/test_argparser.py +++ b/tests/unit/commands/test_argparser.py @@ -20,15 +20,15 @@ """Tests for qutebrowser.commands.argparser.""" import inspect +import enum import pytest from PyQt5.QtCore import QUrl from qutebrowser.commands import argparser, cmdexc -from qutebrowser.utils import usertypes -Enum = usertypes.enum('Enum', ['foo', 'foo_bar']) +Enum = enum.Enum('Enum', ['foo', 'foo_bar']) class TestArgumentParser: diff --git a/tests/unit/commands/test_cmdutils.py b/tests/unit/commands/test_cmdutils.py index 2da13881c..cc4313211 100644 --- a/tests/unit/commands/test_cmdutils.py +++ b/tests/unit/commands/test_cmdutils.py @@ -25,6 +25,7 @@ import sys import logging import types import typing +import enum import pytest @@ -243,7 +244,7 @@ class TestRegister: else: assert pos_args == [('arg', 'arg')] - Enum = usertypes.enum('Test', ['x', 'y']) + Enum = enum.Enum('Test', ['x', 'y']) @pytest.mark.parametrize('typ, inp, choices, expected', [ (int, '42', None, 42), diff --git a/tests/unit/utils/usertypes/test_enum.py b/tests/unit/utils/usertypes/test_enum.py deleted file mode 100644 index a5d2f33e5..000000000 --- a/tests/unit/utils/usertypes/test_enum.py +++ /dev/null @@ -1,74 +0,0 @@ -# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: - -# Copyright 2014-2017 Florian Bruhin (The Compiler) -# -# This file is part of qutebrowser. -# -# qutebrowser is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# qutebrowser is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with qutebrowser. If not, see . - -"""Tests for the Enum class.""" - -from qutebrowser.utils import usertypes - -import pytest - - -@pytest.fixture -def enum(): - return usertypes.enum('Enum', ['one', 'two']) - - -def test_values(enum): - """Test if enum members resolve to the right values.""" - assert enum.one.value == 1 - assert enum.two.value == 2 - - -def test_name(enum): - """Test .name mapping.""" - assert enum.one.name == 'one' - assert enum.two.name == 'two' - - -def test_unknown(enum): - """Test invalid values which should raise an AttributeError.""" - with pytest.raises(AttributeError): - _ = enum.three # flake8: disable=F841 - - -def test_start(): - """Test the start= argument.""" - e = usertypes.enum('Enum', ['three', 'four'], start=3) - assert e.three.value == 3 - assert e.four.value == 4 - - -def test_exit(): - """Make sure the exit status enum is correct.""" - assert usertypes.Exit.ok == 0 - assert usertypes.Exit.reserved == 1 - - -def test_is_int(): - """Test the is_int argument.""" - int_enum = usertypes.enum('Enum', ['item'], is_int=True) - no_int_enum = usertypes.enum('Enum', ['item']) - assert isinstance(int_enum.item, int) - assert not isinstance(no_int_enum.item, int) - - -def test_unique(): - """Make sure elements need to be unique.""" - with pytest.raises(TypeError): - usertypes.enum('Enum', ['item', 'item']) From 5469a238de3a21635a6aea068c8e61ad8da11def Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 2 Nov 2017 19:29:49 +0100 Subject: [PATCH 84/89] Fix docs for new editor replacements See #3100 --- doc/help/settings.asciidoc | 8 ++++---- qutebrowser/config/configdata.yml | 12 ++++++++++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index 69a25e177..a1ddc5429 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -1961,9 +1961,7 @@ Default: +pass:[-1]+ [[editor.command]] === editor.command The editor (and arguments) to use for the `open-editor` command. -Several placeholders are supported. Placeholders are substituted by the -respective value when executing the command. - +Several placeholders are supported. Placeholders are substituted by the respective value when executing the command. `{file}` gets replaced by the filename of the file to be edited. `{line}` gets replaced by the line in which the caret is found in the text. `{column}` gets replaced by the column in which the caret is found in the text. @@ -1976,7 +1974,9 @@ Default: - +pass:[gvim]+ - +pass:[-f]+ -- +pass:[{}]+ +- +pass:[{file}]+ +- +pass:[-c]+ +- +pass:[normal {line}G{column0}l]+ [[editor.encoding]] === editor.encoding diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index 17e1c75b0..0f2a439d3 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -770,8 +770,20 @@ editor.command: desc: >- The editor (and arguments) to use for the `open-editor` command. + Several placeholders are supported. Placeholders are substituted by the + respective value when executing the command. + `{file}` gets replaced by the filename of the file to be edited. + `{line}` gets replaced by the line in which the caret is found in the text. + + `{column}` gets replaced by the column in which the caret is found in the text. + + `{line0}` same as `{line}`, but starting from index 0. + + `{column0}` same as `{column}`, but starting from index 0. + + editor.encoding: type: Encoding default: utf-8 From bb208f4e7736312aa8bfc1763f166b84f2259169 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 2 Nov 2017 19:30:17 +0100 Subject: [PATCH 85/89] Un-hide :open-editor It can be used in normal mode as well, and it's nice to have it discoverable. Fixes #3235. --- doc/help/commands.asciidoc | 14 +++++++------- qutebrowser/browser/commands.py | 3 +-- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc index 5706a6bfb..7b2d8e6c4 100644 --- a/doc/help/commands.asciidoc +++ b/doc/help/commands.asciidoc @@ -60,6 +60,7 @@ It is possible to run or bind multiple commands by separating them with `;;`. |<>|Show a log of past messages. |<>|Open typical prev/next links or navigate using the URL path. |<>|Open a URL in the current/[count]th tab. +|<>|Open an external editor with the currently selected form field. |<>|Print the current/[count]th tab. |<>|Add a new quickmark. |<>|Delete a quickmark. @@ -657,6 +658,12 @@ The tab index to open the URL in. ==== note * This command does not split arguments after the last argument and handles quotes literally. +[[open-editor]] +=== open-editor +Open an external editor with the currently selected form field. + +The editor which should be launched can be configured via the `editor.command` config option. + [[print]] === print Syntax: +:print [*--preview*] [*--pdf* 'file']+ @@ -1158,7 +1165,6 @@ How many steps to zoom out. |<>|Move the cursor or selection to the start of next block. |<>|Move the cursor or selection to the start of previous block. |<>|Do nothing. -|<>|Open an external editor with the currently selected form field. |<>|Accept the current prompt. |<>|Shift the focus of the prompt file completion menu to another item. |<>|Immediately open a download. @@ -1409,12 +1415,6 @@ How many blocks to move. === nop Do nothing. -[[open-editor]] -=== open-editor -Open an external editor with the currently selected form field. - -The editor which should be launched can be configured via the `editor.command` config option. - [[prompt-accept]] === prompt-accept Syntax: +:prompt-accept ['value']+ diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 17179b6ff..3d07e0de7 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1625,8 +1625,7 @@ class CommandDispatcher: self.on_editing_finished, elem)) ed.edit(text, caret_position) - @cmdutils.register(instance='command-dispatcher', hide=True, - scope='window') + @cmdutils.register(instance='command-dispatcher', scope='window') def open_editor(self): """Open an external editor with the currently selected form field. From c28d68173647e8f068c9e12c3102ffc2cb86b624 Mon Sep 17 00:00:00 2001 From: Luca Benci Date: Thu, 2 Nov 2017 19:42:33 +0100 Subject: [PATCH 86/89] Change test to avoid calling private functions --- tests/unit/completion/test_completer.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/tests/unit/completion/test_completer.py b/tests/unit/completion/test_completer.py index 934872f2b..a32241621 100644 --- a/tests/unit/completion/test_completer.py +++ b/tests/unit/completion/test_completer.py @@ -303,12 +303,11 @@ def test_quickcomplete_flicker(status_command_stub, completer_obj, config_stub.val.completion.quick = True _set_cmd_prompt(status_command_stub, ':open |') + completer_obj.schedule_completion_update() + assert completion_widget_stub.set_model.called + completion_widget_stub.set_model.reset_mock() - url = 'http://example.com' - completer_obj._change_completed_part = unittest.mock.Mock() - completer_obj.on_selection_changed(url) - - # no immediate (default is false) - completer_obj._change_completed_part.assert_called_with(url, # text - ['open'], # before - []) # after + # selecting a completion should not re-set the model + completer_obj.on_selection_changed('http://example.com') + completer_obj.schedule_completion_update() + assert not completion_widget_stub.set_model.called From a14ef88acf3bf76159794e556fdd0f3f9365725a Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 2 Nov 2017 20:00:29 +0100 Subject: [PATCH 87/89] Remove some obsolete/deprecated eslint customizations --- qutebrowser/javascript/.eslintrc.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/qutebrowser/javascript/.eslintrc.yaml b/qutebrowser/javascript/.eslintrc.yaml index d82c516d5..421bb70e9 100644 --- a/qutebrowser/javascript/.eslintrc.yaml +++ b/qutebrowser/javascript/.eslintrc.yaml @@ -13,12 +13,9 @@ rules: padded-blocks: ["error", "never"] space-before-function-paren: ["error", "never"] no-underscore-dangle: "off" - vars-on-top: "off" - newline-after-var: "off" camelcase: "off" require-jsdoc: "off" func-style: ["error", "declaration"] - newline-before-return: "off" init-declarations: "off" no-plusplus: "off" no-extra-parens: off From acfb3aa26f3a74b58b0a5f90af9ed924dbf881ac Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 2 Nov 2017 21:59:22 +0100 Subject: [PATCH 88/89] Inject JS into the profile Closes #3216 --- .../browser/webengine/webenginesettings.py | 20 +++++++++++++++++++ qutebrowser/browser/webengine/webenginetab.py | 18 ----------------- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/qutebrowser/browser/webengine/webenginesettings.py b/qutebrowser/browser/webengine/webenginesettings.py index 12503a7c0..f4de9d0d4 100644 --- a/qutebrowser/browser/webengine/webenginesettings.py +++ b/qutebrowser/browser/webengine/webenginesettings.py @@ -193,6 +193,24 @@ def _set_http_headers(profile): profile.setHttpAcceptLanguage(accept_language) +def _init_js(profile): + """Initialize QtWebEngine JavaScript on the given profile.""" + js_code = '\n'.join([ + '"use strict";', + 'window._qutebrowser = {};', + utils.read_file('javascript/scroll.js'), + utils.read_file('javascript/webelem.js'), + ]) + + script = QWebEngineScript() + script.setInjectionPoint(QWebEngineScript.DocumentCreation) + script.setSourceCode(js_code) + script.setWorldId(QWebEngineScript.ApplicationWorld) + # FIXME:qtwebengine What about runsOnSubFrames? + + profile.scripts().insert(script) + + def _update_settings(option): """Update global settings when qwebsettings changed.""" websettings.update_mappings(MAPPINGS, option) @@ -215,11 +233,13 @@ def _init_profiles(): os.path.join(standarddir.data(), 'webengine')) _init_stylesheet(default_profile) _set_http_headers(default_profile) + _init_js(default_profile) private_profile = QWebEngineProfile() assert private_profile.isOffTheRecord() _init_stylesheet(private_profile) _set_http_headers(private_profile) + _init_js(private_profile) if qtutils.version_check('5.8'): default_profile.setSpellCheckEnabled(True) diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 15466ab0d..cf1474834 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -526,27 +526,9 @@ class WebEngineTab(browsertab.AbstractTab): self._set_widget(widget) self._connect_signals() self.backend = usertypes.Backend.QtWebEngine - self._init_js() self._child_event_filter = None self._saved_zoom = None - def _init_js(self): - js_code = '\n'.join([ - '"use strict";', - 'window._qutebrowser = {};', - utils.read_file('javascript/scroll.js'), - utils.read_file('javascript/webelem.js'), - ]) - script = QWebEngineScript() - script.setInjectionPoint(QWebEngineScript.DocumentCreation) - script.setSourceCode(js_code) - - page = self._widget.page() - script.setWorldId(QWebEngineScript.ApplicationWorld) - - # FIXME:qtwebengine what about runsOnSubFrames? - page.scripts().insert(script) - def _install_event_filter(self): self._widget.focusProxy().installEventFilter(self._mouse_event_filter) self._child_event_filter = mouse.ChildEventFilter( From be325853d81ba17568ef3caa30dc840b884fc7fd Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 3 Nov 2017 09:53:45 +0100 Subject: [PATCH 89/89] Revert "Inject JS into the profile" This reverts commit acfb3aa26f3a74b58b0a5f90af9ed924dbf881ac. The related code doesn't really belong in webenginesettings.py after all, and for some reason I don't understand right now this breaks tests in javascript.feature because window._qutebrowser is undefined when running them. --- .../browser/webengine/webenginesettings.py | 20 ------------------- qutebrowser/browser/webengine/webenginetab.py | 18 +++++++++++++++++ 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/qutebrowser/browser/webengine/webenginesettings.py b/qutebrowser/browser/webengine/webenginesettings.py index f4de9d0d4..12503a7c0 100644 --- a/qutebrowser/browser/webengine/webenginesettings.py +++ b/qutebrowser/browser/webengine/webenginesettings.py @@ -193,24 +193,6 @@ def _set_http_headers(profile): profile.setHttpAcceptLanguage(accept_language) -def _init_js(profile): - """Initialize QtWebEngine JavaScript on the given profile.""" - js_code = '\n'.join([ - '"use strict";', - 'window._qutebrowser = {};', - utils.read_file('javascript/scroll.js'), - utils.read_file('javascript/webelem.js'), - ]) - - script = QWebEngineScript() - script.setInjectionPoint(QWebEngineScript.DocumentCreation) - script.setSourceCode(js_code) - script.setWorldId(QWebEngineScript.ApplicationWorld) - # FIXME:qtwebengine What about runsOnSubFrames? - - profile.scripts().insert(script) - - def _update_settings(option): """Update global settings when qwebsettings changed.""" websettings.update_mappings(MAPPINGS, option) @@ -233,13 +215,11 @@ def _init_profiles(): os.path.join(standarddir.data(), 'webengine')) _init_stylesheet(default_profile) _set_http_headers(default_profile) - _init_js(default_profile) private_profile = QWebEngineProfile() assert private_profile.isOffTheRecord() _init_stylesheet(private_profile) _set_http_headers(private_profile) - _init_js(private_profile) if qtutils.version_check('5.8'): default_profile.setSpellCheckEnabled(True) diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index cf1474834..15466ab0d 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -526,9 +526,27 @@ class WebEngineTab(browsertab.AbstractTab): self._set_widget(widget) self._connect_signals() self.backend = usertypes.Backend.QtWebEngine + self._init_js() self._child_event_filter = None self._saved_zoom = None + def _init_js(self): + js_code = '\n'.join([ + '"use strict";', + 'window._qutebrowser = {};', + utils.read_file('javascript/scroll.js'), + utils.read_file('javascript/webelem.js'), + ]) + script = QWebEngineScript() + script.setInjectionPoint(QWebEngineScript.DocumentCreation) + script.setSourceCode(js_code) + + page = self._widget.page() + script.setWorldId(QWebEngineScript.ApplicationWorld) + + # FIXME:qtwebengine what about runsOnSubFrames? + page.scripts().insert(script) + def _install_event_filter(self): self._widget.focusProxy().installEventFilter(self._mouse_event_filter) self._child_event_filter = mouse.ChildEventFilter(