From 695712e50c3871f00959b838909545ff12ae3d6b Mon Sep 17 00:00:00 2001 From: Artur Shaik Date: Thu, 9 Apr 2015 22:55:42 +0600 Subject: [PATCH 01/36] Basic caret and visual modes implementation Allow user switch in caret mode for browsing with caret, and visual mode for select and yank text with keyboard. Default keybindings is c or v for caret mode, and again v for visual mode. All basic movements provided by WebAction enum implemened with vim-like bindings. Yanking with y and Y for selection and clipboard respectively. There is bug/feature in WebKit that after caret enabled, caret doesn't show until mouse click (or sometimes Tab helps). So I add some workaround for that with mouse event. I think should be better aproach. Signed-off-by: Artur Shaik --- qutebrowser/browser/commands.py | 191 ++++++++++++++++++++++++ qutebrowser/browser/webview.py | 38 ++++- qutebrowser/config/configdata.py | 33 ++++ qutebrowser/keyinput/modeman.py | 5 + qutebrowser/keyinput/modeparsers.py | 24 +++ qutebrowser/mainwindow/tabbedbrowser.py | 3 +- qutebrowser/utils/usertypes.py | 2 +- 7 files changed, 293 insertions(+), 3 deletions(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 7dc0a35fd..93488bd04 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -40,6 +40,7 @@ from qutebrowser.browser import webelem from qutebrowser.utils import (message, usertypes, log, qtutils, urlutils, objreg, utils) from qutebrowser.misc import editor +from qutebrowser.keyinput import modeman class CommandDispatcher: @@ -1071,3 +1072,193 @@ class CommandDispatcher: elem.evaluateJavaScript("this.value='{}'".format(text)) except webelem.IsNullError: raise cmdexc.CommandError("Element vanished while editing!") + + @cmdutils.register(instance='command-dispatcher', + modes=[usertypes.KeyMode.caret, usertypes.KeyMode.visual], + hide=True, scope='window') + def move_to_next_line(self): + """Move the cursor or select to the next line.""" + modemanager = modeman._get_modeman(self._win_id) + if modemanager.mode == usertypes.KeyMode.caret: + act = QWebPage.MoveToNextLine + else: + act = QWebPage.SelectNextLine + self._current_widget().triggerPageAction(act) + + @cmdutils.register(instance='command-dispatcher', + modes=[usertypes.KeyMode.caret, usertypes.KeyMode.visual], + hide=True, scope='window') + def move_to_prev_line(self): + """Move the cursor or select to the prev line.""" + modemanager = modeman._get_modeman(self._win_id) + if modemanager.mode == usertypes.KeyMode.caret: + act = QWebPage.MoveToPreviousLine + else: + act = QWebPage.SelectPreviousLine + self._current_widget().triggerPageAction(act) + + @cmdutils.register(instance='command-dispatcher', + modes=[usertypes.KeyMode.caret, usertypes.KeyMode.visual], + hide=True, scope='window') + def move_to_next_char(self): + """Move the cursor or select to the next char.""" + modemanager = modeman._get_modeman(self._win_id) + if modemanager.mode == usertypes.KeyMode.caret: + act = QWebPage.MoveToNextChar + else: + act = QWebPage.SelectNextChar + self._current_widget().triggerPageAction(act) + + @cmdutils.register(instance='command-dispatcher', + modes=[usertypes.KeyMode.caret, usertypes.KeyMode.visual], + hide=True, scope='window') + def move_to_prev_char(self): + """Move the cursor or select to the prev char.""" + modemanager = modeman._get_modeman(self._win_id) + if modemanager.mode == usertypes.KeyMode.caret: + act = QWebPage.MoveToPreviousChar + else: + act = QWebPage.SelectPreviousChar + self._current_widget().triggerPageAction(act) + + @cmdutils.register(instance='command-dispatcher', + modes=[usertypes.KeyMode.caret, usertypes.KeyMode.visual], + hide=True, scope='window') + def move_to_end_of_word(self): + """Move the cursor or select to the next word.""" + modemanager = modeman._get_modeman(self._win_id) + if modemanager.mode == usertypes.KeyMode.caret: + act = QWebPage.MoveToNextWord + else: + act = QWebPage.SelectNextWord + self._current_widget().triggerPageAction(act) + + @cmdutils.register(instance='command-dispatcher', + modes=[usertypes.KeyMode.caret, usertypes.KeyMode.visual], + hide=True, scope='window') + def move_to_next_word(self): + """Move the cursor or select to the next word.""" + modemanager = modeman._get_modeman(self._win_id) + if modemanager.mode == usertypes.KeyMode.caret: + act = [QWebPage.MoveToNextWord, QWebPage.MoveToNextChar] + else: + act = [QWebPage.SelectNextWord, QWebPage.SelectNextChar] + for a in act: + self._current_widget().triggerPageAction(a) + + @cmdutils.register(instance='command-dispatcher', + modes=[usertypes.KeyMode.caret, usertypes.KeyMode.visual], + hide=True, scope='window') + def move_to_prev_word(self): + """Move the cursor or select to the prev word.""" + modemanager = modeman._get_modeman(self._win_id) + if modemanager.mode == usertypes.KeyMode.caret: + act = QWebPage.MoveToPreviousWord + else: + act = QWebPage.SelectPreviousWord + self._current_widget().triggerPageAction(act) + + @cmdutils.register(instance='command-dispatcher', + modes=[usertypes.KeyMode.caret, usertypes.KeyMode.visual], + hide=True, scope='window') + def move_to_start_of_line(self): + """Move the cursor or select to the start of line.""" + modemanager = modeman._get_modeman(self._win_id) + if modemanager.mode == usertypes.KeyMode.caret: + act = QWebPage.MoveToStartOfLine + else: + act = QWebPage.SelectStartOfLine + self._current_widget().triggerPageAction(act) + + @cmdutils.register(instance='command-dispatcher', + modes=[usertypes.KeyMode.caret, usertypes.KeyMode.visual], + hide=True, scope='window') + def move_to_end_of_line(self): + """Move the cursor or select to the end of line.""" + modemanager = modeman._get_modeman(self._win_id) + if modemanager.mode == usertypes.KeyMode.caret: + act = QWebPage.MoveToEndOfLine + else: + act = QWebPage.SelectEndOfLine + self._current_widget().triggerPageAction(act) + + @cmdutils.register(instance='command-dispatcher', + modes=[usertypes.KeyMode.caret, usertypes.KeyMode.visual], + hide=True, scope='window') + def move_to_start_of_block(self): + """Move the cursor or select to the start of block.""" + modemanager = modeman._get_modeman(self._win_id) + if modemanager.mode == usertypes.KeyMode.caret: + act = QWebPage.MoveToStartOfBlock + else: + act = QWebPage.SelectStartOfBlock + self._current_widget().triggerPageAction(act) + + @cmdutils.register(instance='command-dispatcher', + modes=[usertypes.KeyMode.caret, usertypes.KeyMode.visual], + hide=True, scope='window') + def move_to_end_of_block(self): + """Move the cursor or select to the end of block.""" + modemanager = modeman._get_modeman(self._win_id) + if modemanager.mode == usertypes.KeyMode.caret: + act = QWebPage.MoveToEndOfBlock + else: + act = QWebPage.SelectEndOfBlock + self._current_widget().triggerPageAction(act) + + @cmdutils.register(instance='command-dispatcher', + modes=[usertypes.KeyMode.caret, usertypes.KeyMode.visual], + hide=True, scope='window') + def move_to_start_of_document(self): + """Move the cursor or select to the start of document.""" + modemanager = modeman._get_modeman(self._win_id) + if modemanager.mode == usertypes.KeyMode.caret: + act = QWebPage.MoveToStartOfDocument + else: + act = QWebPage.SelectStartOfDocument + self._current_widget().triggerPageAction(act) + + @cmdutils.register(instance='command-dispatcher', + modes=[usertypes.KeyMode.caret, usertypes.KeyMode.visual], + hide=True, scope='window') + def move_to_end_of_document(self): + """Move the cursor or select to the end of document.""" + modemanager = modeman._get_modeman(self._win_id) + if modemanager.mode == usertypes.KeyMode.caret: + act = QWebPage.MoveToEndOfDocument + else: + act = QWebPage.SelectEndOfDocument + self._current_widget().triggerPageAction(act) + + @cmdutils.register(instance='command-dispatcher', + modes=[usertypes.KeyMode.visual], + hide=True, scope='window') + def yank_selected(self, sel=False): + """Yank selected text to the clipboard or primary selection. + + Args: + sel: Use the primary selection instead of the clipboard. + """ + s = self._current_widget().selectedText() + if not self._current_widget().hasSelection() or len(s) == 0: + message.info(self._win_id, "Nothing to yank") + return + + clipboard = QApplication.clipboard() + if sel and clipboard.supportsSelection(): + mode = QClipboard.Selection + target = "primary selection" + else: + mode = QClipboard.Clipboard + target = "clipboard" + log.misc.debug("Yanking to {}: '{}'".format(target, s)) + clipboard.setText(s, mode) + message.info(self._win_id, "{} {} yanked to {}" + .format(len(s), "char" if len(s) == 1 else "chars", target)) + + @cmdutils.register(instance='command-dispatcher', + modes=[usertypes.KeyMode.visual], + hide=True, scope='window') + def drop_selection(self): + """Drop selection and stay in visual mode.""" + self._current_widget().triggerPageAction(QWebPage.MoveToNextChar) diff --git a/qutebrowser/browser/webview.py b/qutebrowser/browser/webview.py index ee88e4089..19c34f9c3 100644 --- a/qutebrowser/browser/webview.py +++ b/qutebrowser/browser/webview.py @@ -23,10 +23,11 @@ import sys import itertools import functools -from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QTimer, QUrl +from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QTimer, QUrl, QPoint from PyQt5.QtWidgets import QApplication, QStyleFactory from PyQt5.QtWebKit import QWebSettings from PyQt5.QtWebKitWidgets import QWebView, QWebPage +from PyQt5.QtGui import QMouseEvent from qutebrowser.config import config from qutebrowser.keyinput import modeman @@ -69,6 +70,7 @@ class WebView(QWebView): _check_insertmode: If True, in mouseReleaseEvent we should check if we need to enter/leave insert mode. _default_zoom_changed: Whether the zoom was changed from the default. + _caret_exist: Whether caret already has focus element Signals: scroll_pos_changed: Scroll percentage of current tab changed. @@ -142,6 +144,7 @@ class WebView(QWebView): self.viewing_source = False self.setZoomFactor(float(config.get('ui', 'default-zoom')) / 100) self._default_zoom_changed = False + self._caret_exist = False objreg.get('config').changed.connect(self.on_config_changed) if config.get('input', 'rocker-gestures'): self.setContextMenuPolicy(Qt.PreventContextMenu) @@ -427,6 +430,31 @@ class WebView(QWebView): "entered.".format(mode)) self.setFocusPolicy(Qt.NoFocus) + self._caret_exist = False + elif mode in (usertypes.KeyMode.caret, usertypes.KeyMode.visual): + self.settings().setAttribute(QWebSettings.CaretBrowsingEnabled, True) + + tabbed_browser = objreg.get('tabbed-browser', scope='window', + window=self.win_id) + if self.tab_id == tabbed_browser._now_focused.tab_id and not self._caret_exist: + """ + Here is a workaround for auto position enabled caret. + Unfortunatly, caret doesn't appear until you click + mouse button on element. I have such behavior in dwb, + so I decided to implement this workaround. + May be should be reworked. + """ + frame = self.page().currentFrame() + halfWidth = frame.scrollBarGeometry(Qt.Horizontal).width() / 2 + point = QPoint(halfWidth,1) + event = QMouseEvent(QMouseEvent.MouseButtonPress, point, point, + point, Qt.LeftButton, Qt.LeftButton, Qt.NoModifier) + QApplication.sendEvent(self, event) + + self._caret_exist = True + else: + self._caret_exist = False + @pyqtSlot(usertypes.KeyMode) def on_mode_left(self, mode): """Restore focus policy if status-input modes were left.""" @@ -434,8 +462,16 @@ class WebView(QWebView): usertypes.KeyMode.yesno): log.webview.debug("Restoring focus policy because mode {} was " "left.".format(mode)) + elif mode in (usertypes.KeyMode.caret, usertypes.KeyMode.visual): + if self.settings().testAttribute(QWebSettings.CaretBrowsingEnabled): + if mode == usertypes.KeyMode.visual and self.hasSelection(): + # Remove selection if exist + self.triggerPageAction(QWebPage.MoveToNextChar) + self.settings().setAttribute(QWebSettings.CaretBrowsingEnabled, False) + self.setFocusPolicy(Qt.WheelFocus) + def createWindow(self, wintype): """Called by Qt when a page wants to create a new window. diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index fea34ab91..079b0da87 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -1019,6 +1019,8 @@ KEY_SECTION_DESC = { " * `prompt-accept`: Confirm the entered value.\n" " * `prompt-yes`: Answer yes to a yes/no question.\n" " * `prompt-no`: Answer no to a yes/no question."), + 'caret': ( + ""), } @@ -1083,6 +1085,7 @@ KEY_DATA = collections.OrderedDict([ ('search-next', ['n']), ('search-prev', ['N']), ('enter-mode insert', ['i']), + ('enter-mode caret', ['c', 'v']), ('yank', ['yy']), ('yank -s', ['yY']), ('yank -t', ['yt']), @@ -1178,6 +1181,36 @@ KEY_DATA = collections.OrderedDict([ ('rl-delete-char', ['']), ('rl-backward-delete-char', ['']), ])), + + ('visual', collections.OrderedDict([ + ('yank-selected', ['y']), + ('yank-selected -s', ['Y']), + ('drop-selection', ['v']), + ('enter-mode caret', ['c']), + ])), + + ('caret', collections.OrderedDict([ + ('enter-mode visual', ['v']), + ('enter-mode normal', ['c']), + ])), + + ('caret,visual', collections.OrderedDict([ + ('move-to-next-line', ['j']), + ('move-to-prev-line', ['k']), + ('move-to-next-char', ['l']), + ('move-to-prev-char', ['h']), + ('move-to-end-of-word', ['e']), + ('move-to-next-word', ['w']), + ('move-to-prev-word', ['b']), + ('move-to-start-of-line', ['0']), + ('move-to-end-of-line', ['$']), + ('move-to-start-of-document', ['gg']), + ('move-to-end-of-document', ['G']), + ('scroll -50 0', ['H']), + ('scroll 0 50', ['J']), + ('scroll 0 -50', ['K']), + ('scroll 50 0', ['L']), + ])), ]) diff --git a/qutebrowser/keyinput/modeman.py b/qutebrowser/keyinput/modeman.py index 4699de8e9..d956cfc08 100644 --- a/qutebrowser/keyinput/modeman.py +++ b/qutebrowser/keyinput/modeman.py @@ -25,6 +25,7 @@ from PyQt5.QtGui import QWindow from PyQt5.QtCore import pyqtSignal, Qt, QObject, QEvent from PyQt5.QtWidgets import QApplication from PyQt5.QtWebKitWidgets import QWebView +from PyQt5.QtWebKit import QWebSettings from qutebrowser.keyinput import modeparsers, keyparser from qutebrowser.config import config @@ -79,6 +80,8 @@ def init(win_id, parent): KM.prompt: keyparser.PassthroughKeyParser(win_id, 'prompt', modeman, warn=False), KM.yesno: modeparsers.PromptKeyParser(win_id, modeman), + KM.caret: modeparsers.CaretKeyParser(win_id, modeman), + KM.visual: modeparsers.VisualKeyParser(win_id, modeman), } objreg.register('keyparsers', keyparsers, scope='window', window=win_id) modeman.destroyed.connect( @@ -93,6 +96,8 @@ def init(win_id, parent): passthrough=True) modeman.register(KM.prompt, keyparsers[KM.prompt].handle, passthrough=True) modeman.register(KM.yesno, keyparsers[KM.yesno].handle) + modeman.register(KM.caret, keyparsers[KM.caret].handle, passthrough=True) + modeman.register(KM.visual, keyparsers[KM.visual].handle, passthrough=True) return modeman diff --git a/qutebrowser/keyinput/modeparsers.py b/qutebrowser/keyinput/modeparsers.py index 7ca4eabc7..4a545f53e 100644 --- a/qutebrowser/keyinput/modeparsers.py +++ b/qutebrowser/keyinput/modeparsers.py @@ -218,3 +218,27 @@ class HintKeyParser(keyparser.CommandKeyParser): hintmanager = objreg.get('hintmanager', scope='tab', window=self._win_id, tab='current') hintmanager.handle_partial_key(keystr) + +class CaretKeyParser(keyparser.CommandKeyParser): + + """KeyParser for Caret mode.""" + + def __init__(self, win_id, parent=None): + super().__init__(win_id, parent, supports_count=True, + supports_chains=True) + self.read_config('caret') + + def __repr__(self): + return utils.get_repr(self) + +class VisualKeyParser(keyparser.CommandKeyParser): + + """KeyParser for Visual mode.""" + + def __init__(self, win_id, parent=None): + super().__init__(win_id, parent, supports_count=True, + supports_chains=True) + self.read_config('visual') + + def __repr__(self): + return utils.get_repr(self) diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index 30cdf1245..5e2d40cf0 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -546,7 +546,8 @@ class TabbedBrowser(tabwidget.TabWidget): tab = self.widget(idx) log.modes.debug("Current tab changed, focusing {!r}".format(tab)) tab.setFocus() - for mode in (usertypes.KeyMode.hint, usertypes.KeyMode.insert): + for mode in (usertypes.KeyMode.hint, usertypes.KeyMode.insert, + usertypes.KeyMode.caret, usertypes.KeyMode.visual): modeman.maybe_leave(self._win_id, mode, 'tab changed') if self._now_focused is not None: objreg.register('last-focused-tab', self._now_focused, update=True, diff --git a/qutebrowser/utils/usertypes.py b/qutebrowser/utils/usertypes.py index 7371f3114..ba5957155 100644 --- a/qutebrowser/utils/usertypes.py +++ b/qutebrowser/utils/usertypes.py @@ -231,7 +231,7 @@ ClickTarget = enum('ClickTarget', ['normal', 'tab', 'tab_bg', 'window']) # Key input modes KeyMode = enum('KeyMode', ['normal', 'hint', 'command', 'yesno', 'prompt', - 'insert', 'passthrough']) + 'insert', 'passthrough', 'caret', 'visual']) # Available command completions From 941eac848efca4298e64c4522e0b2c201f805f44 Mon Sep 17 00:00:00 2001 From: Artur Shaik Date: Mon, 13 Apr 2015 18:37:33 +0600 Subject: [PATCH 02/36] Remove "c" key from normal -> caret mode key bindings --- qutebrowser/config/configdata.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index 079b0da87..0765727b5 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -1085,7 +1085,7 @@ KEY_DATA = collections.OrderedDict([ ('search-next', ['n']), ('search-prev', ['N']), ('enter-mode insert', ['i']), - ('enter-mode caret', ['c', 'v']), + ('enter-mode caret', ['v']), ('yank', ['yy']), ('yank -s', ['yY']), ('yank -t', ['yt']), From a6443231e5644d204088c56b7bfdad436b2311d1 Mon Sep 17 00:00:00 2001 From: Artur Shaik Date: Mon, 13 Apr 2015 19:50:27 +0600 Subject: [PATCH 03/36] Add statusbar coloring for caret and visual modes --- qutebrowser/config/configdata.py | 8 +++++ qutebrowser/mainwindow/statusbar/bar.py | 45 ++++++++++++++++++++----- 2 files changed, 45 insertions(+), 8 deletions(-) diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index 0765727b5..d4b68cb34 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -758,6 +758,14 @@ DATA = collections.OrderedDict([ SettingValue(typ.QssColor(), 'darkgreen'), "Background color of the statusbar in insert mode."), + ('statusbar.bg.caret', + SettingValue(typ.QssColor(), 'purple'), + "Background color of the statusbar in caret mode."), + + ('statusbar.bg.visual', + SettingValue(typ.QssColor(), '#a12dff'), + "Background color of the statusbar in visual mode."), + ('statusbar.progress.bg', SettingValue(typ.QssColor(), 'white'), "Background color of the progress bar."), diff --git a/qutebrowser/mainwindow/statusbar/bar.py b/qutebrowser/mainwindow/statusbar/bar.py index a1c8aabd0..af33d3fe6 100644 --- a/qutebrowser/mainwindow/statusbar/bar.py +++ b/qutebrowser/mainwindow/statusbar/bar.py @@ -91,6 +91,8 @@ class StatusBar(QWidget): _severity = None _prompt_active = False _insert_active = False + _caret_active = False + _visual_active = False STYLESHEET = """ QWidget#StatusBar { @@ -101,6 +103,14 @@ class StatusBar(QWidget): {{ color['statusbar.bg.insert'] }} } + QWidget#StatusBar[caret_active="true"] { + {{ color['statusbar.bg.caret'] }} + } + + QWidget#StatusBar[visual_active="true"] { + {{ color['statusbar.bg.visual'] }} + } + QWidget#StatusBar[prompt_active="true"] { {{ color['statusbar.bg.prompt'] }} } @@ -253,14 +263,31 @@ class StatusBar(QWidget): """Getter for self.insert_active, so it can be used as Qt property.""" return self._insert_active - def _set_insert_active(self, val): - """Setter for self.insert_active. + @pyqtProperty(bool) + def caret_active(self): + """Getter for self.caret_active, so it can be used as Qt property.""" + return self._caret_active + + @pyqtProperty(bool) + def visual_active(self): + """Getter for self.visual_active, so it can be used as Qt property.""" + return self._visual_active + + def _set_mode_active(self, mode, val): + """Setter for self.insert_active, self.caret_active or self.visual_active. Re-set the stylesheet after setting the value, so everything gets updated by Qt properly. """ - log.statusbar.debug("Setting insert_active to {}".format(val)) - self._insert_active = val + if mode == usertypes.KeyMode.insert: + log.statusbar.debug("Setting insert_active to {}".format(val)) + self._insert_active = val + elif mode == usertypes.KeyMode.caret: + log.statusbar.debug("Setting caret_active to {}".format(val)) + self._caret_active = val + elif mode == usertypes.KeyMode.visual: + log.statusbar.debug("Setting visual_active to {}".format(val)) + self._visual_active = val self.setStyleSheet(style.get_stylesheet(self.STYLESHEET)) def _set_mode_text(self, mode): @@ -438,8 +465,9 @@ class StatusBar(QWidget): window=self._win_id) if mode in mode_manager.passthrough: self._set_mode_text(mode.name) - if mode == usertypes.KeyMode.insert: - self._set_insert_active(True) + if mode in (usertypes.KeyMode.insert, usertypes.KeyMode.caret, + usertypes.KeyMode.visual): + self._set_mode_active(mode, True) @pyqtSlot(usertypes.KeyMode, usertypes.KeyMode) def on_mode_left(self, old_mode, new_mode): @@ -451,8 +479,9 @@ class StatusBar(QWidget): self._set_mode_text(new_mode.name) else: self.txt.set_text(self.txt.Text.normal, '') - if old_mode == usertypes.KeyMode.insert: - self._set_insert_active(False) + if old_mode in (usertypes.KeyMode.insert, usertypes.KeyMode.caret, + usertypes.KeyMode.visual): + self._set_mode_active(old_mode, False) @config.change_filter('ui', 'message-timeout') def set_pop_timer_interval(self): From e603d9a2d07b879ec221f515b14fbe84d1e1faab Mon Sep 17 00:00:00 2001 From: Artur Shaik Date: Mon, 13 Apr 2015 19:55:45 +0600 Subject: [PATCH 04/36] Slight modify of autofocus caret Make mouseclick event point slightly down. Add commented tries of more reliable methods of caret focusing. --- qutebrowser/browser/webview.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/qutebrowser/browser/webview.py b/qutebrowser/browser/webview.py index 19c34f9c3..22dca3878 100644 --- a/qutebrowser/browser/webview.py +++ b/qutebrowser/browser/webview.py @@ -446,10 +446,16 @@ class WebView(QWebView): """ frame = self.page().currentFrame() halfWidth = frame.scrollBarGeometry(Qt.Horizontal).width() / 2 - point = QPoint(halfWidth,1) + point = QPoint(halfWidth,10) event = QMouseEvent(QMouseEvent.MouseButtonPress, point, point, point, Qt.LeftButton, Qt.LeftButton, Qt.NoModifier) QApplication.sendEvent(self, event) + #frame.setFocus() + #frame.documentElement().setFocus() + #frame.documentElement().firstChild().setFocus() + #self.page().focusNextPrevChild(True) + #self.page().setContentEditable(True) + #self.triggerPageAction(QWebPage.MoveToNextChar) self._caret_exist = True else: From 9e8c7818715108878ba4767c9055a088c0971f65 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 28 Apr 2015 16:12:23 +0200 Subject: [PATCH 05/36] Use clearFocus/setFocus as workaround. --- qutebrowser/browser/webview.py | 33 ++------------------------------- 1 file changed, 2 insertions(+), 31 deletions(-) diff --git a/qutebrowser/browser/webview.py b/qutebrowser/browser/webview.py index 63461d240..0d7da576c 100644 --- a/qutebrowser/browser/webview.py +++ b/qutebrowser/browser/webview.py @@ -72,7 +72,6 @@ class WebView(QWebView): _check_insertmode: If True, in mouseReleaseEvent we should check if we need to enter/leave insert mode. _default_zoom_changed: Whether the zoom was changed from the default. - _caret_exist: Whether caret already has focus element Signals: scroll_pos_changed: Scroll percentage of current tab changed. @@ -138,7 +137,6 @@ class WebView(QWebView): self.viewing_source = False self.setZoomFactor(float(config.get('ui', 'default-zoom')) / 100) self._default_zoom_changed = False - self._caret_exist = False if config.get('input', 'rocker-gestures'): self.setContextMenuPolicy(Qt.PreventContextMenu) self.urlChanged.connect(self.on_url_changed) @@ -438,37 +436,10 @@ class WebView(QWebView): log.webview.debug("Ignoring focus because mode {} was " "entered.".format(mode)) self.setFocusPolicy(Qt.NoFocus) - - self._caret_exist = False elif mode in (usertypes.KeyMode.caret, usertypes.KeyMode.visual): self.settings().setAttribute(QWebSettings.CaretBrowsingEnabled, True) - - tabbed_browser = objreg.get('tabbed-browser', scope='window', - window=self.win_id) - if self.tab_id == tabbed_browser._now_focused.tab_id and not self._caret_exist: - """ - Here is a workaround for auto position enabled caret. - Unfortunatly, caret doesn't appear until you click - mouse button on element. I have such behavior in dwb, - so I decided to implement this workaround. - May be should be reworked. - """ - frame = self.page().currentFrame() - halfWidth = frame.scrollBarGeometry(Qt.Horizontal).width() / 2 - point = QPoint(halfWidth,10) - event = QMouseEvent(QMouseEvent.MouseButtonPress, point, point, - point, Qt.LeftButton, Qt.LeftButton, Qt.NoModifier) - QApplication.sendEvent(self, event) - #frame.setFocus() - #frame.documentElement().setFocus() - #frame.documentElement().firstChild().setFocus() - #self.page().focusNextPrevChild(True) - #self.page().setContentEditable(True) - #self.triggerPageAction(QWebPage.MoveToNextChar) - - self._caret_exist = True - else: - self._caret_exist = False + self.clearFocus() + self.setFocus(Qt.OtherFocusReason) @pyqtSlot(usertypes.KeyMode) def on_mode_left(self, mode): From 32562c6878468420271da511ca5cb8b9e9c2a2e8 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 28 Apr 2015 16:50:42 +0200 Subject: [PATCH 06/36] Fix lint. --- qutebrowser/browser/commands.py | 136 +++++++++++------------- qutebrowser/browser/webview.py | 11 +- qutebrowser/config/configdata.py | 8 +- qutebrowser/keyinput/modeman.py | 9 +- qutebrowser/keyinput/modeparsers.py | 2 + qutebrowser/mainwindow/statusbar/bar.py | 10 +- qutebrowser/mainwindow/tabbedbrowser.py | 2 +- 7 files changed, 83 insertions(+), 95 deletions(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 37b35a8f9..0adc9c57a 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -40,6 +40,7 @@ from qutebrowser.config import config, configexc from qutebrowser.browser import webelem, inspector from qutebrowser.utils import (message, usertypes, log, qtutils, urlutils, objreg, utils) +from qutebrowser.utils.usertypes import KeyMode from qutebrowser.misc import editor from qutebrowser.keyinput import modeman @@ -1031,8 +1032,7 @@ class CommandDispatcher: self._open(url, tab, bg, window) @cmdutils.register(instance='command-dispatcher', - modes=[usertypes.KeyMode.insert], - hide=True, scope='window') + modes=[KeyMode.insert], hide=True, scope='window') def open_editor(self): """Open an external editor with the currently selected form field. @@ -1152,166 +1152,153 @@ class CommandDispatcher: for _ in range(count): view.search(view.search_text, flags) - @cmdutils.register(instance='command-dispatcher', - modes=[usertypes.KeyMode.caret, usertypes.KeyMode.visual], - hide=True, scope='window') + @cmdutils.register(instance='command-dispatcher', hide=True, + modes=[KeyMode.caret, KeyMode.visual], scope='window') def move_to_next_line(self): """Move the cursor or select to the next line.""" - modemanager = modeman._get_modeman(self._win_id) - if modemanager.mode == usertypes.KeyMode.caret: + modemanager = modeman.get_modeman(self._win_id) + if modemanager.mode == KeyMode.caret: act = QWebPage.MoveToNextLine else: act = QWebPage.SelectNextLine self._current_widget().triggerPageAction(act) - @cmdutils.register(instance='command-dispatcher', - modes=[usertypes.KeyMode.caret, usertypes.KeyMode.visual], - hide=True, scope='window') + @cmdutils.register(instance='command-dispatcher', hide=True, + modes=[KeyMode.caret, KeyMode.visual], scope='window') def move_to_prev_line(self): """Move the cursor or select to the prev line.""" - modemanager = modeman._get_modeman(self._win_id) - if modemanager.mode == usertypes.KeyMode.caret: + modemanager = modeman.get_modeman(self._win_id) + if modemanager.mode == KeyMode.caret: act = QWebPage.MoveToPreviousLine else: act = QWebPage.SelectPreviousLine self._current_widget().triggerPageAction(act) - @cmdutils.register(instance='command-dispatcher', - modes=[usertypes.KeyMode.caret, usertypes.KeyMode.visual], - hide=True, scope='window') + @cmdutils.register(instance='command-dispatcher', hide=True, + modes=[KeyMode.caret, KeyMode.visual], scope='window') def move_to_next_char(self): """Move the cursor or select to the next char.""" - modemanager = modeman._get_modeman(self._win_id) - if modemanager.mode == usertypes.KeyMode.caret: + modemanager = modeman.get_modeman(self._win_id) + if modemanager.mode == KeyMode.caret: act = QWebPage.MoveToNextChar else: act = QWebPage.SelectNextChar self._current_widget().triggerPageAction(act) - @cmdutils.register(instance='command-dispatcher', - modes=[usertypes.KeyMode.caret, usertypes.KeyMode.visual], - hide=True, scope='window') + @cmdutils.register(instance='command-dispatcher', hide=True, + modes=[KeyMode.caret, KeyMode.visual], scope='window') def move_to_prev_char(self): """Move the cursor or select to the prev char.""" - modemanager = modeman._get_modeman(self._win_id) - if modemanager.mode == usertypes.KeyMode.caret: + modemanager = modeman.get_modeman(self._win_id) + if modemanager.mode == KeyMode.caret: act = QWebPage.MoveToPreviousChar else: act = QWebPage.SelectPreviousChar self._current_widget().triggerPageAction(act) - @cmdutils.register(instance='command-dispatcher', - modes=[usertypes.KeyMode.caret, usertypes.KeyMode.visual], - hide=True, scope='window') + @cmdutils.register(instance='command-dispatcher', hide=True, + modes=[KeyMode.caret, KeyMode.visual], scope='window') def move_to_end_of_word(self): """Move the cursor or select to the next word.""" - modemanager = modeman._get_modeman(self._win_id) - if modemanager.mode == usertypes.KeyMode.caret: + modemanager = modeman.get_modeman(self._win_id) + if modemanager.mode == KeyMode.caret: act = QWebPage.MoveToNextWord else: act = QWebPage.SelectNextWord self._current_widget().triggerPageAction(act) - @cmdutils.register(instance='command-dispatcher', - modes=[usertypes.KeyMode.caret, usertypes.KeyMode.visual], - hide=True, scope='window') + @cmdutils.register(instance='command-dispatcher', hide=True, + modes=[KeyMode.caret, KeyMode.visual], + scope='window') def move_to_next_word(self): """Move the cursor or select to the next word.""" - modemanager = modeman._get_modeman(self._win_id) - if modemanager.mode == usertypes.KeyMode.caret: + modemanager = modeman.get_modeman(self._win_id) + if modemanager.mode == KeyMode.caret: act = [QWebPage.MoveToNextWord, QWebPage.MoveToNextChar] else: act = [QWebPage.SelectNextWord, QWebPage.SelectNextChar] for a in act: self._current_widget().triggerPageAction(a) - @cmdutils.register(instance='command-dispatcher', - modes=[usertypes.KeyMode.caret, usertypes.KeyMode.visual], - hide=True, scope='window') + @cmdutils.register(instance='command-dispatcher', hide=True, + modes=[KeyMode.caret, KeyMode.visual], scope='window') def move_to_prev_word(self): """Move the cursor or select to the prev word.""" - modemanager = modeman._get_modeman(self._win_id) - if modemanager.mode == usertypes.KeyMode.caret: + modemanager = modeman.get_modeman(self._win_id) + if modemanager.mode == KeyMode.caret: act = QWebPage.MoveToPreviousWord else: act = QWebPage.SelectPreviousWord self._current_widget().triggerPageAction(act) - @cmdutils.register(instance='command-dispatcher', - modes=[usertypes.KeyMode.caret, usertypes.KeyMode.visual], - hide=True, scope='window') + @cmdutils.register(instance='command-dispatcher', hide=True, + modes=[KeyMode.caret, KeyMode.visual], scope='window') def move_to_start_of_line(self): """Move the cursor or select to the start of line.""" - modemanager = modeman._get_modeman(self._win_id) - if modemanager.mode == usertypes.KeyMode.caret: + modemanager = modeman.get_modeman(self._win_id) + if modemanager.mode == KeyMode.caret: act = QWebPage.MoveToStartOfLine else: act = QWebPage.SelectStartOfLine self._current_widget().triggerPageAction(act) - @cmdutils.register(instance='command-dispatcher', - modes=[usertypes.KeyMode.caret, usertypes.KeyMode.visual], - hide=True, scope='window') + @cmdutils.register(instance='command-dispatcher', hide=True, + modes=[KeyMode.caret, KeyMode.visual], scope='window') def move_to_end_of_line(self): """Move the cursor or select to the end of line.""" - modemanager = modeman._get_modeman(self._win_id) - if modemanager.mode == usertypes.KeyMode.caret: + modemanager = modeman.get_modeman(self._win_id) + if modemanager.mode == KeyMode.caret: act = QWebPage.MoveToEndOfLine else: act = QWebPage.SelectEndOfLine self._current_widget().triggerPageAction(act) - @cmdutils.register(instance='command-dispatcher', - modes=[usertypes.KeyMode.caret, usertypes.KeyMode.visual], - hide=True, scope='window') + @cmdutils.register(instance='command-dispatcher', hide=True, + modes=[KeyMode.caret, KeyMode.visual], scope='window') def move_to_start_of_block(self): """Move the cursor or select to the start of block.""" - modemanager = modeman._get_modeman(self._win_id) - if modemanager.mode == usertypes.KeyMode.caret: + modemanager = modeman.get_modeman(self._win_id) + if modemanager.mode == KeyMode.caret: act = QWebPage.MoveToStartOfBlock else: act = QWebPage.SelectStartOfBlock self._current_widget().triggerPageAction(act) - @cmdutils.register(instance='command-dispatcher', - modes=[usertypes.KeyMode.caret, usertypes.KeyMode.visual], - hide=True, scope='window') + @cmdutils.register(instance='command-dispatcher', hide=True, + modes=[KeyMode.caret, KeyMode.visual], scope='window') def move_to_end_of_block(self): """Move the cursor or select to the end of block.""" - modemanager = modeman._get_modeman(self._win_id) - if modemanager.mode == usertypes.KeyMode.caret: + modemanager = modeman.get_modeman(self._win_id) + if modemanager.mode == KeyMode.caret: act = QWebPage.MoveToEndOfBlock else: act = QWebPage.SelectEndOfBlock self._current_widget().triggerPageAction(act) - @cmdutils.register(instance='command-dispatcher', - modes=[usertypes.KeyMode.caret, usertypes.KeyMode.visual], - hide=True, scope='window') + @cmdutils.register(instance='command-dispatcher', hide=True, + modes=[KeyMode.caret, KeyMode.visual], scope='window') def move_to_start_of_document(self): """Move the cursor or select to the start of document.""" - modemanager = modeman._get_modeman(self._win_id) - if modemanager.mode == usertypes.KeyMode.caret: + modemanager = modeman.get_modeman(self._win_id) + if modemanager.mode == KeyMode.caret: act = QWebPage.MoveToStartOfDocument else: act = QWebPage.SelectStartOfDocument self._current_widget().triggerPageAction(act) - @cmdutils.register(instance='command-dispatcher', - modes=[usertypes.KeyMode.caret, usertypes.KeyMode.visual], - hide=True, scope='window') + @cmdutils.register(instance='command-dispatcher', hide=True, + modes=[KeyMode.caret, KeyMode.visual], scope='window') def move_to_end_of_document(self): """Move the cursor or select to the end of document.""" - modemanager = modeman._get_modeman(self._win_id) - if modemanager.mode == usertypes.KeyMode.caret: + modemanager = modeman.get_modeman(self._win_id) + if modemanager.mode == KeyMode.caret: act = QWebPage.MoveToEndOfDocument else: act = QWebPage.SelectEndOfDocument self._current_widget().triggerPageAction(act) - @cmdutils.register(instance='command-dispatcher', - modes=[usertypes.KeyMode.visual], - hide=True, scope='window') + @cmdutils.register(instance='command-dispatcher', hide=True, + modes=[KeyMode.visual], scope='window') def yank_selected(self, sel=False): """Yank selected text to the clipboard or primary selection. @@ -1332,12 +1319,11 @@ class CommandDispatcher: target = "clipboard" log.misc.debug("Yanking to {}: '{}'".format(target, s)) clipboard.setText(s, mode) - message.info(self._win_id, "{} {} yanked to {}" - .format(len(s), "char" if len(s) == 1 else "chars", target)) + message.info(self._win_id, "{} {} yanked to {}".format( + len(s), "char" if len(s) == 1 else "chars", target)) - @cmdutils.register(instance='command-dispatcher', - modes=[usertypes.KeyMode.visual], - hide=True, scope='window') + @cmdutils.register(instance='command-dispatcher', hide=True, + modes=[KeyMode.visual], scope='window') def drop_selection(self): """Drop selection and stay in visual mode.""" self._current_widget().triggerPageAction(QWebPage.MoveToNextChar) diff --git a/qutebrowser/browser/webview.py b/qutebrowser/browser/webview.py index 0d7da576c..d123d3c21 100644 --- a/qutebrowser/browser/webview.py +++ b/qutebrowser/browser/webview.py @@ -23,11 +23,10 @@ import sys import itertools import functools -from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QTimer, QUrl, QPoint +from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QTimer, QUrl from PyQt5.QtWidgets import QApplication, QStyleFactory from PyQt5.QtWebKit import QWebSettings from PyQt5.QtWebKitWidgets import QWebView, QWebPage -from PyQt5.QtGui import QMouseEvent from qutebrowser.config import config from qutebrowser.keyinput import modeman @@ -437,7 +436,8 @@ class WebView(QWebView): "entered.".format(mode)) self.setFocusPolicy(Qt.NoFocus) elif mode in (usertypes.KeyMode.caret, usertypes.KeyMode.visual): - self.settings().setAttribute(QWebSettings.CaretBrowsingEnabled, True) + settings = self.settings() + settings.setAttribute(QWebSettings.CaretBrowsingEnabled, True) self.clearFocus() self.setFocus(Qt.OtherFocusReason) @@ -449,11 +449,12 @@ class WebView(QWebView): log.webview.debug("Restoring focus policy because mode {} was " "left.".format(mode)) elif mode in (usertypes.KeyMode.caret, usertypes.KeyMode.visual): - if self.settings().testAttribute(QWebSettings.CaretBrowsingEnabled): + settings = self.settings() + if settings.testAttribute(QWebSettings.CaretBrowsingEnabled): if mode == usertypes.KeyMode.visual and self.hasSelection(): # Remove selection if exist self.triggerPageAction(QWebPage.MoveToNextChar) - self.settings().setAttribute(QWebSettings.CaretBrowsingEnabled, False) + settings.setAttribute(QWebSettings.CaretBrowsingEnabled, False) self.setFocusPolicy(Qt.WheelFocus) diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index dd0a7005f..85e532ad4 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -817,12 +817,12 @@ def data(readonly=False): "Background color of the statusbar in insert mode."), ('statusbar.bg.caret', - SettingValue(typ.QssColor(), 'purple'), - "Background color of the statusbar in caret mode."), + SettingValue(typ.QssColor(), 'purple'), + "Background color of the statusbar in caret mode."), ('statusbar.bg.visual', - SettingValue(typ.QssColor(), '#a12dff'), - "Background color of the statusbar in visual mode."), + SettingValue(typ.QssColor(), '#a12dff'), + "Background color of the statusbar in visual mode."), ('statusbar.progress.bg', SettingValue(typ.QssColor(), 'white'), diff --git a/qutebrowser/keyinput/modeman.py b/qutebrowser/keyinput/modeman.py index 44ac96817..665d3c197 100644 --- a/qutebrowser/keyinput/modeman.py +++ b/qutebrowser/keyinput/modeman.py @@ -24,7 +24,6 @@ import functools from PyQt5.QtCore import pyqtSignal, Qt, QObject, QEvent from PyQt5.QtWidgets import QApplication from PyQt5.QtWebKitWidgets import QWebView -from PyQt5.QtWebKit import QWebSettings from qutebrowser.keyinput import modeparsers, keyparser from qutebrowser.config import config @@ -100,25 +99,25 @@ def init(win_id, parent): return modeman -def _get_modeman(win_id): +def get_modeman(win_id): """Get a modemanager object.""" return objreg.get('mode-manager', scope='window', window=win_id) def enter(win_id, mode, reason=None, only_if_normal=False): """Enter the mode 'mode'.""" - _get_modeman(win_id).enter(mode, reason, only_if_normal) + get_modeman(win_id).enter(mode, reason, only_if_normal) def leave(win_id, mode, reason=None): """Leave the mode 'mode'.""" - _get_modeman(win_id).leave(mode, reason) + get_modeman(win_id).leave(mode, reason) def maybe_leave(win_id, mode, reason=None): """Convenience method to leave 'mode' without exceptions.""" try: - _get_modeman(win_id).leave(mode, reason) + get_modeman(win_id).leave(mode, reason) except NotInModeError as e: # This is rather likely to happen, so we only log to debug log. log.modes.debug("{} (leave reason: {})".format(e, reason)) diff --git a/qutebrowser/keyinput/modeparsers.py b/qutebrowser/keyinput/modeparsers.py index 4a545f53e..121172798 100644 --- a/qutebrowser/keyinput/modeparsers.py +++ b/qutebrowser/keyinput/modeparsers.py @@ -219,6 +219,7 @@ class HintKeyParser(keyparser.CommandKeyParser): window=self._win_id, tab='current') hintmanager.handle_partial_key(keystr) + class CaretKeyParser(keyparser.CommandKeyParser): """KeyParser for Caret mode.""" @@ -231,6 +232,7 @@ class CaretKeyParser(keyparser.CommandKeyParser): def __repr__(self): return utils.get_repr(self) + class VisualKeyParser(keyparser.CommandKeyParser): """KeyParser for Visual mode.""" diff --git a/qutebrowser/mainwindow/statusbar/bar.py b/qutebrowser/mainwindow/statusbar/bar.py index af33d3fe6..0b041c5dc 100644 --- a/qutebrowser/mainwindow/statusbar/bar.py +++ b/qutebrowser/mainwindow/statusbar/bar.py @@ -274,7 +274,7 @@ class StatusBar(QWidget): return self._visual_active def _set_mode_active(self, mode, val): - """Setter for self.insert_active, self.caret_active or self.visual_active. + """Setter for self.{insert,caret,visual}_active. Re-set the stylesheet after setting the value, so everything gets updated by Qt properly. @@ -465,8 +465,8 @@ class StatusBar(QWidget): window=self._win_id) if mode in mode_manager.passthrough: self._set_mode_text(mode.name) - if mode in (usertypes.KeyMode.insert, usertypes.KeyMode.caret, - usertypes.KeyMode.visual): + if mode in (usertypes.KeyMode.insert, usertypes.KeyMode.caret, + usertypes.KeyMode.visual): self._set_mode_active(mode, True) @pyqtSlot(usertypes.KeyMode, usertypes.KeyMode) @@ -479,8 +479,8 @@ class StatusBar(QWidget): self._set_mode_text(new_mode.name) else: self.txt.set_text(self.txt.Text.normal, '') - if old_mode in (usertypes.KeyMode.insert, usertypes.KeyMode.caret, - usertypes.KeyMode.visual): + if old_mode in (usertypes.KeyMode.insert, usertypes.KeyMode.caret, + usertypes.KeyMode.visual): self._set_mode_active(old_mode, False) @config.change_filter('ui', 'message-timeout') diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index b5c7655a5..d65b3ac20 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -519,7 +519,7 @@ class TabbedBrowser(tabwidget.TabWidget): log.modes.debug("Current tab changed, focusing {!r}".format(tab)) tab.setFocus() for mode in (usertypes.KeyMode.hint, usertypes.KeyMode.insert, - usertypes.KeyMode.caret, usertypes.KeyMode.visual): + usertypes.KeyMode.caret, usertypes.KeyMode.visual): modeman.maybe_leave(self._win_id, mode, 'tab changed') if self._now_focused is not None: objreg.register('last-focused-tab', self._now_focused, update=True, From aeaa20c3b7fbd00d17f2da01a03dbec3e8ad9464 Mon Sep 17 00:00:00 2001 From: Artur Shaik Date: Mon, 4 May 2015 18:00:40 +0600 Subject: [PATCH 07/36] Disable support count for CaretKeyParser Allow using '0' for move caret to beginnig of the line. --- qutebrowser/keyinput/modeparsers.py | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/qutebrowser/keyinput/modeparsers.py b/qutebrowser/keyinput/modeparsers.py index 121172798..068884c0b 100644 --- a/qutebrowser/keyinput/modeparsers.py +++ b/qutebrowser/keyinput/modeparsers.py @@ -228,19 +228,8 @@ class CaretKeyParser(keyparser.CommandKeyParser): super().__init__(win_id, parent, supports_count=True, supports_chains=True) self.read_config('caret') + self._supports_count = False def __repr__(self): return utils.get_repr(self) - -class VisualKeyParser(keyparser.CommandKeyParser): - - """KeyParser for Visual mode.""" - - def __init__(self, win_id, parent=None): - super().__init__(win_id, parent, supports_count=True, - supports_chains=True) - self.read_config('visual') - - def __repr__(self): - return utils.get_repr(self) From d594798db8ecb635ecb7b9ce89d0eb22c27ecc57 Mon Sep 17 00:00:00 2001 From: Artur Shaik Date: Tue, 5 May 2015 10:18:24 +0600 Subject: [PATCH 08/36] Implement caret selection and positioning Added option to webview for selection enabled caret mode. In status bar checking value of this option to identificate about it. Added bindings: for toggle selection mode, drop selection and keep selection mode enabled. In webview added javascript snippet to position caret at top of the viewport after caret enabling. This code mostly was taken from cVim sources. --- qutebrowser/browser/commands.py | 118 +++++++++++++----------- qutebrowser/browser/webview.py | 91 +++++++++++++++++- qutebrowser/config/configdata.py | 19 ++-- qutebrowser/keyinput/basekeyparser.py | 1 + qutebrowser/keyinput/modeman.py | 2 - qutebrowser/mainwindow/statusbar/bar.py | 35 ++++--- qutebrowser/mainwindow/tabbedbrowser.py | 2 +- qutebrowser/utils/usertypes.py | 2 +- 8 files changed, 178 insertions(+), 92 deletions(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 0adc9c57a..56d516c7e 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1153,152 +1153,152 @@ class CommandDispatcher: view.search(view.search_text, flags) @cmdutils.register(instance='command-dispatcher', hide=True, - modes=[KeyMode.caret, KeyMode.visual], scope='window') + modes=[KeyMode.caret], scope='window') def move_to_next_line(self): """Move the cursor or select to the next line.""" - modemanager = modeman.get_modeman(self._win_id) - if modemanager.mode == KeyMode.caret: + webview = self._current_widget() + if not webview.selection_enabled: act = QWebPage.MoveToNextLine else: act = QWebPage.SelectNextLine - self._current_widget().triggerPageAction(act) + webview.triggerPageAction(act) @cmdutils.register(instance='command-dispatcher', hide=True, - modes=[KeyMode.caret, KeyMode.visual], scope='window') + modes=[KeyMode.caret], scope='window') def move_to_prev_line(self): """Move the cursor or select to the prev line.""" - modemanager = modeman.get_modeman(self._win_id) - if modemanager.mode == KeyMode.caret: + webview = self._current_widget() + if not webview.selection_enabled: act = QWebPage.MoveToPreviousLine else: act = QWebPage.SelectPreviousLine - self._current_widget().triggerPageAction(act) + webview.triggerPageAction(act) @cmdutils.register(instance='command-dispatcher', hide=True, - modes=[KeyMode.caret, KeyMode.visual], scope='window') + modes=[KeyMode.caret], scope='window') def move_to_next_char(self): """Move the cursor or select to the next char.""" - modemanager = modeman.get_modeman(self._win_id) - if modemanager.mode == KeyMode.caret: + webview = self._current_widget() + if not webview.selection_enabled: act = QWebPage.MoveToNextChar else: act = QWebPage.SelectNextChar - self._current_widget().triggerPageAction(act) + webview.triggerPageAction(act) @cmdutils.register(instance='command-dispatcher', hide=True, - modes=[KeyMode.caret, KeyMode.visual], scope='window') + modes=[KeyMode.caret], scope='window') def move_to_prev_char(self): """Move the cursor or select to the prev char.""" - modemanager = modeman.get_modeman(self._win_id) - if modemanager.mode == KeyMode.caret: + webview = self._current_widget() + if not webview.selection_enabled: act = QWebPage.MoveToPreviousChar else: act = QWebPage.SelectPreviousChar - self._current_widget().triggerPageAction(act) + webview.triggerPageAction(act) @cmdutils.register(instance='command-dispatcher', hide=True, - modes=[KeyMode.caret, KeyMode.visual], scope='window') + modes=[KeyMode.caret], scope='window') def move_to_end_of_word(self): """Move the cursor or select to the next word.""" - modemanager = modeman.get_modeman(self._win_id) - if modemanager.mode == KeyMode.caret: + webview = self._current_widget() + if not webview.selection_enabled: act = QWebPage.MoveToNextWord else: act = QWebPage.SelectNextWord - self._current_widget().triggerPageAction(act) + webview.triggerPageAction(act) @cmdutils.register(instance='command-dispatcher', hide=True, - modes=[KeyMode.caret, KeyMode.visual], + modes=[KeyMode.caret], scope='window') def move_to_next_word(self): """Move the cursor or select to the next word.""" - modemanager = modeman.get_modeman(self._win_id) - if modemanager.mode == KeyMode.caret: + webview = self._current_widget() + if not webview.selection_enabled: act = [QWebPage.MoveToNextWord, QWebPage.MoveToNextChar] else: act = [QWebPage.SelectNextWord, QWebPage.SelectNextChar] for a in act: - self._current_widget().triggerPageAction(a) + webview.triggerPageAction(a) @cmdutils.register(instance='command-dispatcher', hide=True, - modes=[KeyMode.caret, KeyMode.visual], scope='window') + modes=[KeyMode.caret], scope='window') def move_to_prev_word(self): """Move the cursor or select to the prev word.""" - modemanager = modeman.get_modeman(self._win_id) - if modemanager.mode == KeyMode.caret: + webview = self._current_widget() + if not webview.selection_enabled: act = QWebPage.MoveToPreviousWord else: act = QWebPage.SelectPreviousWord - self._current_widget().triggerPageAction(act) + webview.triggerPageAction(act) @cmdutils.register(instance='command-dispatcher', hide=True, - modes=[KeyMode.caret, KeyMode.visual], scope='window') + modes=[KeyMode.caret], scope='window') def move_to_start_of_line(self): """Move the cursor or select to the start of line.""" - modemanager = modeman.get_modeman(self._win_id) - if modemanager.mode == KeyMode.caret: + webview = self._current_widget() + if not webview.selection_enabled: act = QWebPage.MoveToStartOfLine else: act = QWebPage.SelectStartOfLine - self._current_widget().triggerPageAction(act) + webview.triggerPageAction(act) @cmdutils.register(instance='command-dispatcher', hide=True, - modes=[KeyMode.caret, KeyMode.visual], scope='window') + modes=[KeyMode.caret], scope='window') def move_to_end_of_line(self): """Move the cursor or select to the end of line.""" - modemanager = modeman.get_modeman(self._win_id) - if modemanager.mode == KeyMode.caret: + webview = self._current_widget() + if not webview.selection_enabled: act = QWebPage.MoveToEndOfLine else: act = QWebPage.SelectEndOfLine - self._current_widget().triggerPageAction(act) + webview.triggerPageAction(act) @cmdutils.register(instance='command-dispatcher', hide=True, - modes=[KeyMode.caret, KeyMode.visual], scope='window') + modes=[KeyMode.caret], scope='window') def move_to_start_of_block(self): """Move the cursor or select to the start of block.""" - modemanager = modeman.get_modeman(self._win_id) - if modemanager.mode == KeyMode.caret: + webview = self._current_widget() + if not webview.selection_enabled: act = QWebPage.MoveToStartOfBlock else: act = QWebPage.SelectStartOfBlock - self._current_widget().triggerPageAction(act) + webview.triggerPageAction(act) @cmdutils.register(instance='command-dispatcher', hide=True, - modes=[KeyMode.caret, KeyMode.visual], scope='window') + modes=[KeyMode.caret], scope='window') def move_to_end_of_block(self): """Move the cursor or select to the end of block.""" - modemanager = modeman.get_modeman(self._win_id) - if modemanager.mode == KeyMode.caret: + webview = self._current_widget() + if not webview.selection_enabled: act = QWebPage.MoveToEndOfBlock else: act = QWebPage.SelectEndOfBlock - self._current_widget().triggerPageAction(act) + webview.triggerPageAction(act) @cmdutils.register(instance='command-dispatcher', hide=True, - modes=[KeyMode.caret, KeyMode.visual], scope='window') + modes=[KeyMode.caret], scope='window') def move_to_start_of_document(self): """Move the cursor or select to the start of document.""" - modemanager = modeman.get_modeman(self._win_id) - if modemanager.mode == KeyMode.caret: + webview = self._current_widget() + if not webview.selection_enabled: act = QWebPage.MoveToStartOfDocument else: act = QWebPage.SelectStartOfDocument - self._current_widget().triggerPageAction(act) + webview.triggerPageAction(act) @cmdutils.register(instance='command-dispatcher', hide=True, - modes=[KeyMode.caret, KeyMode.visual], scope='window') + modes=[KeyMode.caret], scope='window') def move_to_end_of_document(self): """Move the cursor or select to the end of document.""" - modemanager = modeman.get_modeman(self._win_id) - if modemanager.mode == KeyMode.caret: + webview = self._current_widget() + if not webview.selection_enabled: act = QWebPage.MoveToEndOfDocument else: act = QWebPage.SelectEndOfDocument - self._current_widget().triggerPageAction(act) + webview.triggerPageAction(act) @cmdutils.register(instance='command-dispatcher', hide=True, - modes=[KeyMode.visual], scope='window') + modes=[KeyMode.caret], scope='window') def yank_selected(self, sel=False): """Yank selected text to the clipboard or primary selection. @@ -1323,9 +1323,17 @@ class CommandDispatcher: len(s), "char" if len(s) == 1 else "chars", target)) @cmdutils.register(instance='command-dispatcher', hide=True, - modes=[KeyMode.visual], scope='window') + modes=[KeyMode.caret], scope='window') + def toggle_selection(self): + """Toggle caret selection mode.""" + self._current_widget().selection_enabled = not self._current_widget().selection_enabled + mainwindow = objreg.get('main-window', scope='window', window=self._win_id) + mainwindow.status.on_mode_entered(usertypes.KeyMode.caret) + + @cmdutils.register(instance='command-dispatcher', hide=True, + modes=[KeyMode.caret], scope='window') def drop_selection(self): - """Drop selection and stay in visual mode.""" + """Drop selection and keep selection mode enabled.""" self._current_widget().triggerPageAction(QWebPage.MoveToNextChar) @cmdutils.register(instance='command-dispatcher', scope='window', diff --git a/qutebrowser/browser/webview.py b/qutebrowser/browser/webview.py index d123d3c21..2afd9dac3 100644 --- a/qutebrowser/browser/webview.py +++ b/qutebrowser/browser/webview.py @@ -106,6 +106,7 @@ class WebView(QWebView): self.keep_icon = False self.search_text = None self.search_flags = 0 + self.selection_enabled = False; self.init_neighborlist() cfg = objreg.get('config') cfg.changed.connect(self.init_neighborlist) @@ -180,6 +181,79 @@ class WebView(QWebView): self.load_status = val self.load_status_changed.emit(val.name) + def _position_caret(self): + """ + JS snippet to position caret at top of the screen. + Was borrowed from cVim source code + """ + self.page().currentFrame().evaluateJavaScript(""" + + function isElementInViewport(node) { + var i; + var boundingRect = node.getClientRects()[0] || node.getBoundingClientRect(); + if (boundingRect.width <= 1 && boundingRect.height <= 1) { + var rects = node.getClientRects(); + for (i = 0; i < rects.length; i++) { + if (rects[i].width > rects[0].height && rects[i].height > rects[0].height) { + boundingRect = rects[i]; + } + } + } + if (boundingRect === void 0) return null; + if (boundingRect.top > innerHeight || boundingRect.left > innerWidth) { + return null; + } + if (boundingRect.width <= 1 || boundingRect.height <= 1) { + var children = node.children; + var visibleChildNode = false; + for (i = 0, l = children.length; i < l; ++i) { + boundingRect = children[i].getClientRects()[0] || children[i].getBoundingClientRect(); + if (boundingRect.width > 1 && boundingRect.height > 1) { + visibleChildNode = true; + break; + } + } + if (visibleChildNode === false) return null; + } + if (boundingRect.top + boundingRect.height < 10 || boundingRect.left + boundingRect.width < -10) { + return null; + } + var computedStyle = window.getComputedStyle(node, null); + if (computedStyle.visibility !== 'visible' || + computedStyle.display === 'none' || + node.hasAttribute('disabled') || + parseInt(computedStyle.width, '10') === 0 || + parseInt(computedStyle.height, '10') === 0) { + return null; + } + return boundingRect.top >= -20; + } + + var walker = document.createTreeWalker(document.body, 4, null, false); + var node; + var textNodes = []; + 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; + if (isElementInViewport(element.parentElement)) { + el = element; + break; + } + } + if (el !== undefined) { + var range = document.createRange(); + range.setStart(el, 0); + range.setEnd(el, 0); + var sel = window.getSelection(); + sel.removeAllRanges(); + sel.addRange(range); + } + """) + @pyqtSlot(str, str) def on_config_changed(self, section, option): """Reinitialize the zoom neighborlist if related config changed.""" @@ -435,11 +509,17 @@ class WebView(QWebView): log.webview.debug("Ignoring focus because mode {} was " "entered.".format(mode)) self.setFocusPolicy(Qt.NoFocus) - elif mode in (usertypes.KeyMode.caret, usertypes.KeyMode.visual): + elif mode == usertypes.KeyMode.caret: settings = self.settings() settings.setAttribute(QWebSettings.CaretBrowsingEnabled, True) - self.clearFocus() - self.setFocus(Qt.OtherFocusReason) + self.selection_enabled = False + + tabbed = objreg.get('tabbed-browser', scope='window', window=self.win_id) + if tabbed.currentWidget().tab_id == self.tab_id: + self.clearFocus() + self.setFocus(Qt.OtherFocusReason) + + self._position_caret() @pyqtSlot(usertypes.KeyMode) def on_mode_left(self, mode): @@ -448,13 +528,14 @@ class WebView(QWebView): usertypes.KeyMode.yesno): log.webview.debug("Restoring focus policy because mode {} was " "left.".format(mode)) - elif mode in (usertypes.KeyMode.caret, usertypes.KeyMode.visual): + elif mode == usertypes.KeyMode.caret: settings = self.settings() if settings.testAttribute(QWebSettings.CaretBrowsingEnabled): - if mode == usertypes.KeyMode.visual and self.hasSelection(): + if self.selection_enabled and self.hasSelection(): # Remove selection if exist self.triggerPageAction(QWebPage.MoveToNextChar) settings.setAttribute(QWebSettings.CaretBrowsingEnabled, False) + self.selection_enabled = False; self.setFocusPolicy(Qt.WheelFocus) diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index 85e532ad4..219ab0f95 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -820,9 +820,9 @@ def data(readonly=False): SettingValue(typ.QssColor(), 'purple'), "Background color of the statusbar in caret mode."), - ('statusbar.bg.visual', + ('statusbar.bg.caret_selection', SettingValue(typ.QssColor(), '#a12dff'), - "Background color of the statusbar in visual mode."), + "Background color of the statusbar in caret selection enabled mode."), ('statusbar.progress.bg', SettingValue(typ.QssColor(), 'white'), @@ -1259,19 +1259,10 @@ KEY_DATA = collections.OrderedDict([ ('rl-backward-delete-char', ['']), ])), - ('visual', collections.OrderedDict([ - ('yank-selected', ['y']), - ('yank-selected -s', ['Y']), - ('drop-selection', ['v']), - ('enter-mode caret', ['c']), - ])), - ('caret', collections.OrderedDict([ - ('enter-mode visual', ['v']), + ('toggle-selection', ['']), + ('drop-selection', ['']), ('enter-mode normal', ['c']), - ])), - - ('caret,visual', collections.OrderedDict([ ('move-to-next-line', ['j']), ('move-to-prev-line', ['k']), ('move-to-next-char', ['l']), @@ -1283,6 +1274,8 @@ KEY_DATA = collections.OrderedDict([ ('move-to-end-of-line', ['$']), ('move-to-start-of-document', ['gg']), ('move-to-end-of-document', ['G']), + ('yank-selected', ['y']), + ('yank-selected -p', ['Y']), ('scroll -50 0', ['H']), ('scroll 0 50', ['J']), ('scroll 0 -50', ['K']), diff --git a/qutebrowser/keyinput/basekeyparser.py b/qutebrowser/keyinput/basekeyparser.py index c5e70f414..a7408de89 100644 --- a/qutebrowser/keyinput/basekeyparser.py +++ b/qutebrowser/keyinput/basekeyparser.py @@ -180,6 +180,7 @@ class BaseKeyParser(QObject): count, cmd_input = self._split_count() + print(count, cmd_input) if not cmd_input: # Only a count, no command yet, but we handled it return self.Match.other diff --git a/qutebrowser/keyinput/modeman.py b/qutebrowser/keyinput/modeman.py index 665d3c197..3d4760849 100644 --- a/qutebrowser/keyinput/modeman.py +++ b/qutebrowser/keyinput/modeman.py @@ -79,7 +79,6 @@ def init(win_id, parent): warn=False), KM.yesno: modeparsers.PromptKeyParser(win_id, modeman), KM.caret: modeparsers.CaretKeyParser(win_id, modeman), - KM.visual: modeparsers.VisualKeyParser(win_id, modeman), } objreg.register('keyparsers', keyparsers, scope='window', window=win_id) modeman.destroyed.connect( @@ -95,7 +94,6 @@ def init(win_id, parent): modeman.register(KM.prompt, keyparsers[KM.prompt].handle, passthrough=True) modeman.register(KM.yesno, keyparsers[KM.yesno].handle) modeman.register(KM.caret, keyparsers[KM.caret].handle, passthrough=True) - modeman.register(KM.visual, keyparsers[KM.visual].handle, passthrough=True) return modeman diff --git a/qutebrowser/mainwindow/statusbar/bar.py b/qutebrowser/mainwindow/statusbar/bar.py index 0b041c5dc..54c77d3dc 100644 --- a/qutebrowser/mainwindow/statusbar/bar.py +++ b/qutebrowser/mainwindow/statusbar/bar.py @@ -92,7 +92,7 @@ class StatusBar(QWidget): _prompt_active = False _insert_active = False _caret_active = False - _visual_active = False + _caret_selection_active = False STYLESHEET = """ QWidget#StatusBar { @@ -107,8 +107,8 @@ class StatusBar(QWidget): {{ color['statusbar.bg.caret'] }} } - QWidget#StatusBar[visual_active="true"] { - {{ color['statusbar.bg.visual'] }} + QWidget#StatusBar[caret_selection_active="true"] { + {{ color['statusbar.bg.caret_selection'] }} } QWidget#StatusBar[prompt_active="true"] { @@ -269,12 +269,12 @@ class StatusBar(QWidget): return self._caret_active @pyqtProperty(bool) - def visual_active(self): - """Getter for self.visual_active, so it can be used as Qt property.""" - return self._visual_active + def caret_selection_active(self): + """Getter for self.caret_active, so it can be used as Qt property.""" + return self._caret_selection_active def _set_mode_active(self, mode, val): - """Setter for self.{insert,caret,visual}_active. + """Setter for self.{insert,caret}_active. Re-set the stylesheet after setting the value, so everything gets updated by Qt properly. @@ -284,10 +284,17 @@ class StatusBar(QWidget): self._insert_active = val elif mode == usertypes.KeyMode.caret: log.statusbar.debug("Setting caret_active to {}".format(val)) - self._caret_active = val - elif mode == usertypes.KeyMode.visual: - log.statusbar.debug("Setting visual_active to {}".format(val)) - self._visual_active = val + webview = objreg.get("tabbed-browser", scope="window", window=self._win_id).currentWidget() + if val and webview.selection_enabled: + self._set_mode_text("{} selection".format(mode.name)) + self._caret_selection_active = val + self._caret_active = False + else: + if val: + self._set_mode_text(mode.name) + self._caret_active = val + self._caret_selection_active = False + self.setStyleSheet(style.get_stylesheet(self.STYLESHEET)) def _set_mode_text(self, mode): @@ -465,8 +472,7 @@ class StatusBar(QWidget): window=self._win_id) if mode in mode_manager.passthrough: self._set_mode_text(mode.name) - if mode in (usertypes.KeyMode.insert, usertypes.KeyMode.caret, - usertypes.KeyMode.visual): + if mode in (usertypes.KeyMode.insert, usertypes.KeyMode.caret): self._set_mode_active(mode, True) @pyqtSlot(usertypes.KeyMode, usertypes.KeyMode) @@ -479,8 +485,7 @@ class StatusBar(QWidget): self._set_mode_text(new_mode.name) else: self.txt.set_text(self.txt.Text.normal, '') - if old_mode in (usertypes.KeyMode.insert, usertypes.KeyMode.caret, - usertypes.KeyMode.visual): + if old_mode in (usertypes.KeyMode.insert, usertypes.KeyMode.caret): self._set_mode_active(old_mode, False) @config.change_filter('ui', 'message-timeout') diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index d65b3ac20..df236b077 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -519,7 +519,7 @@ class TabbedBrowser(tabwidget.TabWidget): log.modes.debug("Current tab changed, focusing {!r}".format(tab)) tab.setFocus() for mode in (usertypes.KeyMode.hint, usertypes.KeyMode.insert, - usertypes.KeyMode.caret, usertypes.KeyMode.visual): + usertypes.KeyMode.caret): modeman.maybe_leave(self._win_id, mode, 'tab changed') if self._now_focused is not None: objreg.register('last-focused-tab', self._now_focused, update=True, diff --git a/qutebrowser/utils/usertypes.py b/qutebrowser/utils/usertypes.py index ba5957155..ba568ab56 100644 --- a/qutebrowser/utils/usertypes.py +++ b/qutebrowser/utils/usertypes.py @@ -231,7 +231,7 @@ ClickTarget = enum('ClickTarget', ['normal', 'tab', 'tab_bg', 'window']) # Key input modes KeyMode = enum('KeyMode', ['normal', 'hint', 'command', 'yesno', 'prompt', - 'insert', 'passthrough', 'caret', 'visual']) + 'insert', 'passthrough', 'caret']) # Available command completions From 489c913e586a37a6e982ddf152d6ed7fb6984b8c Mon Sep 17 00:00:00 2001 From: Artur Shaik Date: Tue, 5 May 2015 10:18:24 +0600 Subject: [PATCH 09/36] Implement caret selection and positioning Added option to webview for selection enabled caret mode. In status bar checking value of this option to identificate about it. Added bindings: for toggle selection mode, drop selection and keep selection mode enabled. In webview added javascript snippet to position caret at top of the viewport after caret enabling. This code mostly was taken from cVim sources. --- qutebrowser/browser/commands.py | 118 +++++++++++++----------- qutebrowser/browser/webview.py | 91 +++++++++++++++++- qutebrowser/config/configdata.py | 19 ++-- qutebrowser/keyinput/modeman.py | 2 - qutebrowser/mainwindow/statusbar/bar.py | 35 ++++--- qutebrowser/mainwindow/tabbedbrowser.py | 2 +- qutebrowser/utils/usertypes.py | 2 +- 7 files changed, 177 insertions(+), 92 deletions(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 0adc9c57a..56d516c7e 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1153,152 +1153,152 @@ class CommandDispatcher: view.search(view.search_text, flags) @cmdutils.register(instance='command-dispatcher', hide=True, - modes=[KeyMode.caret, KeyMode.visual], scope='window') + modes=[KeyMode.caret], scope='window') def move_to_next_line(self): """Move the cursor or select to the next line.""" - modemanager = modeman.get_modeman(self._win_id) - if modemanager.mode == KeyMode.caret: + webview = self._current_widget() + if not webview.selection_enabled: act = QWebPage.MoveToNextLine else: act = QWebPage.SelectNextLine - self._current_widget().triggerPageAction(act) + webview.triggerPageAction(act) @cmdutils.register(instance='command-dispatcher', hide=True, - modes=[KeyMode.caret, KeyMode.visual], scope='window') + modes=[KeyMode.caret], scope='window') def move_to_prev_line(self): """Move the cursor or select to the prev line.""" - modemanager = modeman.get_modeman(self._win_id) - if modemanager.mode == KeyMode.caret: + webview = self._current_widget() + if not webview.selection_enabled: act = QWebPage.MoveToPreviousLine else: act = QWebPage.SelectPreviousLine - self._current_widget().triggerPageAction(act) + webview.triggerPageAction(act) @cmdutils.register(instance='command-dispatcher', hide=True, - modes=[KeyMode.caret, KeyMode.visual], scope='window') + modes=[KeyMode.caret], scope='window') def move_to_next_char(self): """Move the cursor or select to the next char.""" - modemanager = modeman.get_modeman(self._win_id) - if modemanager.mode == KeyMode.caret: + webview = self._current_widget() + if not webview.selection_enabled: act = QWebPage.MoveToNextChar else: act = QWebPage.SelectNextChar - self._current_widget().triggerPageAction(act) + webview.triggerPageAction(act) @cmdutils.register(instance='command-dispatcher', hide=True, - modes=[KeyMode.caret, KeyMode.visual], scope='window') + modes=[KeyMode.caret], scope='window') def move_to_prev_char(self): """Move the cursor or select to the prev char.""" - modemanager = modeman.get_modeman(self._win_id) - if modemanager.mode == KeyMode.caret: + webview = self._current_widget() + if not webview.selection_enabled: act = QWebPage.MoveToPreviousChar else: act = QWebPage.SelectPreviousChar - self._current_widget().triggerPageAction(act) + webview.triggerPageAction(act) @cmdutils.register(instance='command-dispatcher', hide=True, - modes=[KeyMode.caret, KeyMode.visual], scope='window') + modes=[KeyMode.caret], scope='window') def move_to_end_of_word(self): """Move the cursor or select to the next word.""" - modemanager = modeman.get_modeman(self._win_id) - if modemanager.mode == KeyMode.caret: + webview = self._current_widget() + if not webview.selection_enabled: act = QWebPage.MoveToNextWord else: act = QWebPage.SelectNextWord - self._current_widget().triggerPageAction(act) + webview.triggerPageAction(act) @cmdutils.register(instance='command-dispatcher', hide=True, - modes=[KeyMode.caret, KeyMode.visual], + modes=[KeyMode.caret], scope='window') def move_to_next_word(self): """Move the cursor or select to the next word.""" - modemanager = modeman.get_modeman(self._win_id) - if modemanager.mode == KeyMode.caret: + webview = self._current_widget() + if not webview.selection_enabled: act = [QWebPage.MoveToNextWord, QWebPage.MoveToNextChar] else: act = [QWebPage.SelectNextWord, QWebPage.SelectNextChar] for a in act: - self._current_widget().triggerPageAction(a) + webview.triggerPageAction(a) @cmdutils.register(instance='command-dispatcher', hide=True, - modes=[KeyMode.caret, KeyMode.visual], scope='window') + modes=[KeyMode.caret], scope='window') def move_to_prev_word(self): """Move the cursor or select to the prev word.""" - modemanager = modeman.get_modeman(self._win_id) - if modemanager.mode == KeyMode.caret: + webview = self._current_widget() + if not webview.selection_enabled: act = QWebPage.MoveToPreviousWord else: act = QWebPage.SelectPreviousWord - self._current_widget().triggerPageAction(act) + webview.triggerPageAction(act) @cmdutils.register(instance='command-dispatcher', hide=True, - modes=[KeyMode.caret, KeyMode.visual], scope='window') + modes=[KeyMode.caret], scope='window') def move_to_start_of_line(self): """Move the cursor or select to the start of line.""" - modemanager = modeman.get_modeman(self._win_id) - if modemanager.mode == KeyMode.caret: + webview = self._current_widget() + if not webview.selection_enabled: act = QWebPage.MoveToStartOfLine else: act = QWebPage.SelectStartOfLine - self._current_widget().triggerPageAction(act) + webview.triggerPageAction(act) @cmdutils.register(instance='command-dispatcher', hide=True, - modes=[KeyMode.caret, KeyMode.visual], scope='window') + modes=[KeyMode.caret], scope='window') def move_to_end_of_line(self): """Move the cursor or select to the end of line.""" - modemanager = modeman.get_modeman(self._win_id) - if modemanager.mode == KeyMode.caret: + webview = self._current_widget() + if not webview.selection_enabled: act = QWebPage.MoveToEndOfLine else: act = QWebPage.SelectEndOfLine - self._current_widget().triggerPageAction(act) + webview.triggerPageAction(act) @cmdutils.register(instance='command-dispatcher', hide=True, - modes=[KeyMode.caret, KeyMode.visual], scope='window') + modes=[KeyMode.caret], scope='window') def move_to_start_of_block(self): """Move the cursor or select to the start of block.""" - modemanager = modeman.get_modeman(self._win_id) - if modemanager.mode == KeyMode.caret: + webview = self._current_widget() + if not webview.selection_enabled: act = QWebPage.MoveToStartOfBlock else: act = QWebPage.SelectStartOfBlock - self._current_widget().triggerPageAction(act) + webview.triggerPageAction(act) @cmdutils.register(instance='command-dispatcher', hide=True, - modes=[KeyMode.caret, KeyMode.visual], scope='window') + modes=[KeyMode.caret], scope='window') def move_to_end_of_block(self): """Move the cursor or select to the end of block.""" - modemanager = modeman.get_modeman(self._win_id) - if modemanager.mode == KeyMode.caret: + webview = self._current_widget() + if not webview.selection_enabled: act = QWebPage.MoveToEndOfBlock else: act = QWebPage.SelectEndOfBlock - self._current_widget().triggerPageAction(act) + webview.triggerPageAction(act) @cmdutils.register(instance='command-dispatcher', hide=True, - modes=[KeyMode.caret, KeyMode.visual], scope='window') + modes=[KeyMode.caret], scope='window') def move_to_start_of_document(self): """Move the cursor or select to the start of document.""" - modemanager = modeman.get_modeman(self._win_id) - if modemanager.mode == KeyMode.caret: + webview = self._current_widget() + if not webview.selection_enabled: act = QWebPage.MoveToStartOfDocument else: act = QWebPage.SelectStartOfDocument - self._current_widget().triggerPageAction(act) + webview.triggerPageAction(act) @cmdutils.register(instance='command-dispatcher', hide=True, - modes=[KeyMode.caret, KeyMode.visual], scope='window') + modes=[KeyMode.caret], scope='window') def move_to_end_of_document(self): """Move the cursor or select to the end of document.""" - modemanager = modeman.get_modeman(self._win_id) - if modemanager.mode == KeyMode.caret: + webview = self._current_widget() + if not webview.selection_enabled: act = QWebPage.MoveToEndOfDocument else: act = QWebPage.SelectEndOfDocument - self._current_widget().triggerPageAction(act) + webview.triggerPageAction(act) @cmdutils.register(instance='command-dispatcher', hide=True, - modes=[KeyMode.visual], scope='window') + modes=[KeyMode.caret], scope='window') def yank_selected(self, sel=False): """Yank selected text to the clipboard or primary selection. @@ -1323,9 +1323,17 @@ class CommandDispatcher: len(s), "char" if len(s) == 1 else "chars", target)) @cmdutils.register(instance='command-dispatcher', hide=True, - modes=[KeyMode.visual], scope='window') + modes=[KeyMode.caret], scope='window') + def toggle_selection(self): + """Toggle caret selection mode.""" + self._current_widget().selection_enabled = not self._current_widget().selection_enabled + mainwindow = objreg.get('main-window', scope='window', window=self._win_id) + mainwindow.status.on_mode_entered(usertypes.KeyMode.caret) + + @cmdutils.register(instance='command-dispatcher', hide=True, + modes=[KeyMode.caret], scope='window') def drop_selection(self): - """Drop selection and stay in visual mode.""" + """Drop selection and keep selection mode enabled.""" self._current_widget().triggerPageAction(QWebPage.MoveToNextChar) @cmdutils.register(instance='command-dispatcher', scope='window', diff --git a/qutebrowser/browser/webview.py b/qutebrowser/browser/webview.py index d123d3c21..2afd9dac3 100644 --- a/qutebrowser/browser/webview.py +++ b/qutebrowser/browser/webview.py @@ -106,6 +106,7 @@ class WebView(QWebView): self.keep_icon = False self.search_text = None self.search_flags = 0 + self.selection_enabled = False; self.init_neighborlist() cfg = objreg.get('config') cfg.changed.connect(self.init_neighborlist) @@ -180,6 +181,79 @@ class WebView(QWebView): self.load_status = val self.load_status_changed.emit(val.name) + def _position_caret(self): + """ + JS snippet to position caret at top of the screen. + Was borrowed from cVim source code + """ + self.page().currentFrame().evaluateJavaScript(""" + + function isElementInViewport(node) { + var i; + var boundingRect = node.getClientRects()[0] || node.getBoundingClientRect(); + if (boundingRect.width <= 1 && boundingRect.height <= 1) { + var rects = node.getClientRects(); + for (i = 0; i < rects.length; i++) { + if (rects[i].width > rects[0].height && rects[i].height > rects[0].height) { + boundingRect = rects[i]; + } + } + } + if (boundingRect === void 0) return null; + if (boundingRect.top > innerHeight || boundingRect.left > innerWidth) { + return null; + } + if (boundingRect.width <= 1 || boundingRect.height <= 1) { + var children = node.children; + var visibleChildNode = false; + for (i = 0, l = children.length; i < l; ++i) { + boundingRect = children[i].getClientRects()[0] || children[i].getBoundingClientRect(); + if (boundingRect.width > 1 && boundingRect.height > 1) { + visibleChildNode = true; + break; + } + } + if (visibleChildNode === false) return null; + } + if (boundingRect.top + boundingRect.height < 10 || boundingRect.left + boundingRect.width < -10) { + return null; + } + var computedStyle = window.getComputedStyle(node, null); + if (computedStyle.visibility !== 'visible' || + computedStyle.display === 'none' || + node.hasAttribute('disabled') || + parseInt(computedStyle.width, '10') === 0 || + parseInt(computedStyle.height, '10') === 0) { + return null; + } + return boundingRect.top >= -20; + } + + var walker = document.createTreeWalker(document.body, 4, null, false); + var node; + var textNodes = []; + 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; + if (isElementInViewport(element.parentElement)) { + el = element; + break; + } + } + if (el !== undefined) { + var range = document.createRange(); + range.setStart(el, 0); + range.setEnd(el, 0); + var sel = window.getSelection(); + sel.removeAllRanges(); + sel.addRange(range); + } + """) + @pyqtSlot(str, str) def on_config_changed(self, section, option): """Reinitialize the zoom neighborlist if related config changed.""" @@ -435,11 +509,17 @@ class WebView(QWebView): log.webview.debug("Ignoring focus because mode {} was " "entered.".format(mode)) self.setFocusPolicy(Qt.NoFocus) - elif mode in (usertypes.KeyMode.caret, usertypes.KeyMode.visual): + elif mode == usertypes.KeyMode.caret: settings = self.settings() settings.setAttribute(QWebSettings.CaretBrowsingEnabled, True) - self.clearFocus() - self.setFocus(Qt.OtherFocusReason) + self.selection_enabled = False + + tabbed = objreg.get('tabbed-browser', scope='window', window=self.win_id) + if tabbed.currentWidget().tab_id == self.tab_id: + self.clearFocus() + self.setFocus(Qt.OtherFocusReason) + + self._position_caret() @pyqtSlot(usertypes.KeyMode) def on_mode_left(self, mode): @@ -448,13 +528,14 @@ class WebView(QWebView): usertypes.KeyMode.yesno): log.webview.debug("Restoring focus policy because mode {} was " "left.".format(mode)) - elif mode in (usertypes.KeyMode.caret, usertypes.KeyMode.visual): + elif mode == usertypes.KeyMode.caret: settings = self.settings() if settings.testAttribute(QWebSettings.CaretBrowsingEnabled): - if mode == usertypes.KeyMode.visual and self.hasSelection(): + if self.selection_enabled and self.hasSelection(): # Remove selection if exist self.triggerPageAction(QWebPage.MoveToNextChar) settings.setAttribute(QWebSettings.CaretBrowsingEnabled, False) + self.selection_enabled = False; self.setFocusPolicy(Qt.WheelFocus) diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index 85e532ad4..219ab0f95 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -820,9 +820,9 @@ def data(readonly=False): SettingValue(typ.QssColor(), 'purple'), "Background color of the statusbar in caret mode."), - ('statusbar.bg.visual', + ('statusbar.bg.caret_selection', SettingValue(typ.QssColor(), '#a12dff'), - "Background color of the statusbar in visual mode."), + "Background color of the statusbar in caret selection enabled mode."), ('statusbar.progress.bg', SettingValue(typ.QssColor(), 'white'), @@ -1259,19 +1259,10 @@ KEY_DATA = collections.OrderedDict([ ('rl-backward-delete-char', ['']), ])), - ('visual', collections.OrderedDict([ - ('yank-selected', ['y']), - ('yank-selected -s', ['Y']), - ('drop-selection', ['v']), - ('enter-mode caret', ['c']), - ])), - ('caret', collections.OrderedDict([ - ('enter-mode visual', ['v']), + ('toggle-selection', ['']), + ('drop-selection', ['']), ('enter-mode normal', ['c']), - ])), - - ('caret,visual', collections.OrderedDict([ ('move-to-next-line', ['j']), ('move-to-prev-line', ['k']), ('move-to-next-char', ['l']), @@ -1283,6 +1274,8 @@ KEY_DATA = collections.OrderedDict([ ('move-to-end-of-line', ['$']), ('move-to-start-of-document', ['gg']), ('move-to-end-of-document', ['G']), + ('yank-selected', ['y']), + ('yank-selected -p', ['Y']), ('scroll -50 0', ['H']), ('scroll 0 50', ['J']), ('scroll 0 -50', ['K']), diff --git a/qutebrowser/keyinput/modeman.py b/qutebrowser/keyinput/modeman.py index 665d3c197..3d4760849 100644 --- a/qutebrowser/keyinput/modeman.py +++ b/qutebrowser/keyinput/modeman.py @@ -79,7 +79,6 @@ def init(win_id, parent): warn=False), KM.yesno: modeparsers.PromptKeyParser(win_id, modeman), KM.caret: modeparsers.CaretKeyParser(win_id, modeman), - KM.visual: modeparsers.VisualKeyParser(win_id, modeman), } objreg.register('keyparsers', keyparsers, scope='window', window=win_id) modeman.destroyed.connect( @@ -95,7 +94,6 @@ def init(win_id, parent): modeman.register(KM.prompt, keyparsers[KM.prompt].handle, passthrough=True) modeman.register(KM.yesno, keyparsers[KM.yesno].handle) modeman.register(KM.caret, keyparsers[KM.caret].handle, passthrough=True) - modeman.register(KM.visual, keyparsers[KM.visual].handle, passthrough=True) return modeman diff --git a/qutebrowser/mainwindow/statusbar/bar.py b/qutebrowser/mainwindow/statusbar/bar.py index 0b041c5dc..54c77d3dc 100644 --- a/qutebrowser/mainwindow/statusbar/bar.py +++ b/qutebrowser/mainwindow/statusbar/bar.py @@ -92,7 +92,7 @@ class StatusBar(QWidget): _prompt_active = False _insert_active = False _caret_active = False - _visual_active = False + _caret_selection_active = False STYLESHEET = """ QWidget#StatusBar { @@ -107,8 +107,8 @@ class StatusBar(QWidget): {{ color['statusbar.bg.caret'] }} } - QWidget#StatusBar[visual_active="true"] { - {{ color['statusbar.bg.visual'] }} + QWidget#StatusBar[caret_selection_active="true"] { + {{ color['statusbar.bg.caret_selection'] }} } QWidget#StatusBar[prompt_active="true"] { @@ -269,12 +269,12 @@ class StatusBar(QWidget): return self._caret_active @pyqtProperty(bool) - def visual_active(self): - """Getter for self.visual_active, so it can be used as Qt property.""" - return self._visual_active + def caret_selection_active(self): + """Getter for self.caret_active, so it can be used as Qt property.""" + return self._caret_selection_active def _set_mode_active(self, mode, val): - """Setter for self.{insert,caret,visual}_active. + """Setter for self.{insert,caret}_active. Re-set the stylesheet after setting the value, so everything gets updated by Qt properly. @@ -284,10 +284,17 @@ class StatusBar(QWidget): self._insert_active = val elif mode == usertypes.KeyMode.caret: log.statusbar.debug("Setting caret_active to {}".format(val)) - self._caret_active = val - elif mode == usertypes.KeyMode.visual: - log.statusbar.debug("Setting visual_active to {}".format(val)) - self._visual_active = val + webview = objreg.get("tabbed-browser", scope="window", window=self._win_id).currentWidget() + if val and webview.selection_enabled: + self._set_mode_text("{} selection".format(mode.name)) + self._caret_selection_active = val + self._caret_active = False + else: + if val: + self._set_mode_text(mode.name) + self._caret_active = val + self._caret_selection_active = False + self.setStyleSheet(style.get_stylesheet(self.STYLESHEET)) def _set_mode_text(self, mode): @@ -465,8 +472,7 @@ class StatusBar(QWidget): window=self._win_id) if mode in mode_manager.passthrough: self._set_mode_text(mode.name) - if mode in (usertypes.KeyMode.insert, usertypes.KeyMode.caret, - usertypes.KeyMode.visual): + if mode in (usertypes.KeyMode.insert, usertypes.KeyMode.caret): self._set_mode_active(mode, True) @pyqtSlot(usertypes.KeyMode, usertypes.KeyMode) @@ -479,8 +485,7 @@ class StatusBar(QWidget): self._set_mode_text(new_mode.name) else: self.txt.set_text(self.txt.Text.normal, '') - if old_mode in (usertypes.KeyMode.insert, usertypes.KeyMode.caret, - usertypes.KeyMode.visual): + if old_mode in (usertypes.KeyMode.insert, usertypes.KeyMode.caret): self._set_mode_active(old_mode, False) @config.change_filter('ui', 'message-timeout') diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index d65b3ac20..df236b077 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -519,7 +519,7 @@ class TabbedBrowser(tabwidget.TabWidget): log.modes.debug("Current tab changed, focusing {!r}".format(tab)) tab.setFocus() for mode in (usertypes.KeyMode.hint, usertypes.KeyMode.insert, - usertypes.KeyMode.caret, usertypes.KeyMode.visual): + usertypes.KeyMode.caret): modeman.maybe_leave(self._win_id, mode, 'tab changed') if self._now_focused is not None: objreg.register('last-focused-tab', self._now_focused, update=True, diff --git a/qutebrowser/utils/usertypes.py b/qutebrowser/utils/usertypes.py index ba5957155..ba568ab56 100644 --- a/qutebrowser/utils/usertypes.py +++ b/qutebrowser/utils/usertypes.py @@ -231,7 +231,7 @@ ClickTarget = enum('ClickTarget', ['normal', 'tab', 'tab_bg', 'window']) # Key input modes KeyMode = enum('KeyMode', ['normal', 'hint', 'command', 'yesno', 'prompt', - 'insert', 'passthrough', 'caret', 'visual']) + 'insert', 'passthrough', 'caret']) # Available command completions From 178d0dfa5882c5c414dbcb18e89c036765720a73 Mon Sep 17 00:00:00 2001 From: Artur Shaik Date: Thu, 7 May 2015 11:51:10 +0600 Subject: [PATCH 10/36] Add count for actions. Zero key treat as command. --- qutebrowser/browser/commands.py | 80 +++++++++++++++------------ qutebrowser/keyinput/basekeyparser.py | 4 +- qutebrowser/keyinput/modeparsers.py | 1 - 3 files changed, 48 insertions(+), 37 deletions(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 56d516c7e..e395e0306 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1153,127 +1153,137 @@ class CommandDispatcher: view.search(view.search_text, flags) @cmdutils.register(instance='command-dispatcher', hide=True, - modes=[KeyMode.caret], scope='window') - def move_to_next_line(self): + modes=[KeyMode.caret], scope='window', count='count') + def move_to_next_line(self, count=1): """Move the cursor or select to the next line.""" webview = self._current_widget() if not webview.selection_enabled: act = QWebPage.MoveToNextLine else: act = QWebPage.SelectNextLine - webview.triggerPageAction(act) + for _ in range(count): + webview.triggerPageAction(act) @cmdutils.register(instance='command-dispatcher', hide=True, - modes=[KeyMode.caret], scope='window') - def move_to_prev_line(self): + modes=[KeyMode.caret], scope='window', count='count') + def move_to_prev_line(self, count=1): """Move the cursor or select to the prev line.""" webview = self._current_widget() if not webview.selection_enabled: act = QWebPage.MoveToPreviousLine else: act = QWebPage.SelectPreviousLine - webview.triggerPageAction(act) + for _ in range(count): + webview.triggerPageAction(act) @cmdutils.register(instance='command-dispatcher', hide=True, - modes=[KeyMode.caret], scope='window') - def move_to_next_char(self): + modes=[KeyMode.caret], scope='window', count='count') + def move_to_next_char(self, count=1): """Move the cursor or select to the next char.""" webview = self._current_widget() if not webview.selection_enabled: act = QWebPage.MoveToNextChar else: act = QWebPage.SelectNextChar - webview.triggerPageAction(act) + for _ in range(count): + webview.triggerPageAction(act) @cmdutils.register(instance='command-dispatcher', hide=True, - modes=[KeyMode.caret], scope='window') - def move_to_prev_char(self): + modes=[KeyMode.caret], scope='window', count='count') + def move_to_prev_char(self, count=1): """Move the cursor or select to the prev char.""" webview = self._current_widget() if not webview.selection_enabled: act = QWebPage.MoveToPreviousChar else: act = QWebPage.SelectPreviousChar - webview.triggerPageAction(act) + for _ in range(count): + webview.triggerPageAction(act) @cmdutils.register(instance='command-dispatcher', hide=True, - modes=[KeyMode.caret], scope='window') - def move_to_end_of_word(self): + modes=[KeyMode.caret], scope='window', count='count') + def move_to_end_of_word(self, count=1): """Move the cursor or select to the next word.""" webview = self._current_widget() if not webview.selection_enabled: act = QWebPage.MoveToNextWord else: act = QWebPage.SelectNextWord - webview.triggerPageAction(act) + for _ in range(count): + webview.triggerPageAction(act) @cmdutils.register(instance='command-dispatcher', hide=True, - modes=[KeyMode.caret], - scope='window') - def move_to_next_word(self): + modes=[KeyMode.caret], scope='window', count='count') + def move_to_next_word(self, count=1): """Move the cursor or select to the next word.""" webview = self._current_widget() if not webview.selection_enabled: act = [QWebPage.MoveToNextWord, QWebPage.MoveToNextChar] else: act = [QWebPage.SelectNextWord, QWebPage.SelectNextChar] - for a in act: - webview.triggerPageAction(a) + for _ in range(count): + for a in act: + webview.triggerPageAction(a) @cmdutils.register(instance='command-dispatcher', hide=True, - modes=[KeyMode.caret], scope='window') - def move_to_prev_word(self): + modes=[KeyMode.caret], scope='window', count='count') + def move_to_prev_word(self, count=1): """Move the cursor or select to the prev word.""" webview = self._current_widget() if not webview.selection_enabled: act = QWebPage.MoveToPreviousWord else: act = QWebPage.SelectPreviousWord - webview.triggerPageAction(act) + for _ in range(count): + webview.triggerPageAction(act) @cmdutils.register(instance='command-dispatcher', hide=True, - modes=[KeyMode.caret], scope='window') - def move_to_start_of_line(self): + modes=[KeyMode.caret], scope='window', count='count') + def move_to_start_of_line(self, count=1): """Move the cursor or select to the start of line.""" webview = self._current_widget() if not webview.selection_enabled: act = QWebPage.MoveToStartOfLine else: act = QWebPage.SelectStartOfLine - webview.triggerPageAction(act) + for _ in range(count): + webview.triggerPageAction(act) @cmdutils.register(instance='command-dispatcher', hide=True, - modes=[KeyMode.caret], scope='window') - def move_to_end_of_line(self): + modes=[KeyMode.caret], scope='window', count='count') + def move_to_end_of_line(self, count=1): """Move the cursor or select to the end of line.""" webview = self._current_widget() if not webview.selection_enabled: act = QWebPage.MoveToEndOfLine else: act = QWebPage.SelectEndOfLine - webview.triggerPageAction(act) + for _ in range(count): + webview.triggerPageAction(act) @cmdutils.register(instance='command-dispatcher', hide=True, - modes=[KeyMode.caret], scope='window') - def move_to_start_of_block(self): + modes=[KeyMode.caret], scope='window', count='count') + def move_to_start_of_block(self, count=1): """Move the cursor or select to the start of block.""" webview = self._current_widget() if not webview.selection_enabled: act = QWebPage.MoveToStartOfBlock else: act = QWebPage.SelectStartOfBlock - webview.triggerPageAction(act) + for _ in range(count): + webview.triggerPageAction(act) @cmdutils.register(instance='command-dispatcher', hide=True, - modes=[KeyMode.caret], scope='window') - def move_to_end_of_block(self): + modes=[KeyMode.caret], scope='window', count='count') + def move_to_end_of_block(self, count=1): """Move the cursor or select to the end of block.""" webview = self._current_widget() if not webview.selection_enabled: act = QWebPage.MoveToEndOfBlock else: act = QWebPage.SelectEndOfBlock - webview.triggerPageAction(act) + for _ in range(count): + webview.triggerPageAction(act) @cmdutils.register(instance='command-dispatcher', hide=True, modes=[KeyMode.caret], scope='window') diff --git a/qutebrowser/keyinput/basekeyparser.py b/qutebrowser/keyinput/basekeyparser.py index a7408de89..b52a39824 100644 --- a/qutebrowser/keyinput/basekeyparser.py +++ b/qutebrowser/keyinput/basekeyparser.py @@ -137,6 +137,9 @@ class BaseKeyParser(QObject): (countstr, cmd_input) = re.match(r'^(\d*)(.*)', self._keystring).groups() count = int(countstr) if countstr else None + if count == 0 and not cmd_input: + cmd_input = self._keystring + count = None else: cmd_input = self._keystring count = None @@ -180,7 +183,6 @@ class BaseKeyParser(QObject): count, cmd_input = self._split_count() - print(count, cmd_input) if not cmd_input: # Only a count, no command yet, but we handled it return self.Match.other diff --git a/qutebrowser/keyinput/modeparsers.py b/qutebrowser/keyinput/modeparsers.py index 068884c0b..8ccdf6ad3 100644 --- a/qutebrowser/keyinput/modeparsers.py +++ b/qutebrowser/keyinput/modeparsers.py @@ -228,7 +228,6 @@ class CaretKeyParser(keyparser.CommandKeyParser): super().__init__(win_id, parent, supports_count=True, supports_chains=True) self.read_config('caret') - self._supports_count = False def __repr__(self): return utils.get_repr(self) From d936be450be524af677bf8fa4867c184460fffd7 Mon Sep 17 00:00:00 2001 From: Artur Shaik Date: Thu, 7 May 2015 12:19:35 +0600 Subject: [PATCH 11/36] Add jumps through text blocks in caret mode. --- qutebrowser/browser/commands.py | 48 +++++++++++++++++++++++++------- qutebrowser/config/configdata.py | 4 +++ 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index e395e0306..65bac7901 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1263,27 +1263,55 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', hide=True, modes=[KeyMode.caret], scope='window', count='count') - def move_to_start_of_block(self, count=1): - """Move the cursor or select to the start of block.""" + def move_to_start_of_next_block(self, count=1): + """Move the cursor or select to the start of next block.""" webview = self._current_widget() if not webview.selection_enabled: - act = QWebPage.MoveToStartOfBlock + act = [QWebPage.MoveToEndOfBlock, QWebPage.MoveToNextLine, QWebPage.MoveToStartOfBlock] else: - act = QWebPage.SelectStartOfBlock + act = [QWebPage.SelectEndOfBlock, QWebPage.SelectNextLine, QWebPage.SelectStartOfBlock] for _ in range(count): - webview.triggerPageAction(act) + for a in act: + webview.triggerPageAction(a) @cmdutils.register(instance='command-dispatcher', hide=True, modes=[KeyMode.caret], scope='window', count='count') - def move_to_end_of_block(self, count=1): - """Move the cursor or select to the end of block.""" + def move_to_start_of_prev_block(self, count=1): + """Move the cursor or select to the start of previous block.""" webview = self._current_widget() if not webview.selection_enabled: - act = QWebPage.MoveToEndOfBlock + act = [QWebPage.MoveToStartOfBlock, QWebPage.MoveToPreviousLine, QWebPage.MoveToStartOfBlock] else: - act = QWebPage.SelectEndOfBlock + act = [QWebPage.SelectStartOfBlock, QWebPage.SelectPreviousLine, QWebPage.SelectStartOfBlock] for _ in range(count): - webview.triggerPageAction(act) + for a in act: + webview.triggerPageAction(a) + + @cmdutils.register(instance='command-dispatcher', hide=True, + modes=[KeyMode.caret], scope='window', count='count') + def move_to_end_of_next_block(self, count=1): + """Move the cursor or select to the end of next block.""" + webview = self._current_widget() + if not webview.selection_enabled: + act = [QWebPage.MoveToEndOfBlock, QWebPage.MoveToNextLine, QWebPage.MoveToEndOfBlock] + else: + act = [QWebPage.SelectEndOfBlock, QWebPage.SelectNextLine, QWebPage.SelectEndOfBlock] + for _ in range(count): + for a in act: + webview.triggerPageAction(a) + + @cmdutils.register(instance='command-dispatcher', hide=True, + modes=[KeyMode.caret], scope='window', count='count') + def move_to_end_of_prev_block(self, count=1): + """Move the cursor or select to the end of previous block.""" + webview = self._current_widget() + if not webview.selection_enabled: + act = [QWebPage.MoveToStartOfBlock, QWebPage.MoveToPreviousLine, QWebPage.MoveToEndOfBlock] + else: + act = [QWebPage.SelectStartOfBlock, QWebPage.SelectPreviousLine, QWebPage.SelectEndOfBlock] + for _ in range(count): + for a in act: + webview.triggerPageAction(a) @cmdutils.register(instance='command-dispatcher', hide=True, modes=[KeyMode.caret], scope='window') diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index 219ab0f95..e7da24caf 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -1270,6 +1270,10 @@ KEY_DATA = collections.OrderedDict([ ('move-to-end-of-word', ['e']), ('move-to-next-word', ['w']), ('move-to-prev-word', ['b']), + ('move-to-start-of-next-block', [']']), + ('move-to-start-of-prev-block', ['[']), + ('move-to-end-of-next-block', ['}']), + ('move-to-end-of-prev-block', ['{']), ('move-to-start-of-line', ['0']), ('move-to-end-of-line', ['$']), ('move-to-start-of-document', ['gg']), From 778ad5df3a8fa9b9f7f5eb28ad89e7c5f8d6aa6c Mon Sep 17 00:00:00 2001 From: Artur Shaik Date: Thu, 7 May 2015 12:23:09 +0600 Subject: [PATCH 12/36] Comment clean. --- qutebrowser/mainwindow/statusbar/bar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/mainwindow/statusbar/bar.py b/qutebrowser/mainwindow/statusbar/bar.py index 54c77d3dc..e8311a1e7 100644 --- a/qutebrowser/mainwindow/statusbar/bar.py +++ b/qutebrowser/mainwindow/statusbar/bar.py @@ -270,7 +270,7 @@ class StatusBar(QWidget): @pyqtProperty(bool) def caret_selection_active(self): - """Getter for self.caret_active, so it can be used as Qt property.""" + """Getter for self.caret_selection_active, so it can be used as Qt property.""" return self._caret_selection_active def _set_mode_active(self, mode, val): From 57cad14714c57553b7d60d8b02ae422a733052ad Mon Sep 17 00:00:00 2001 From: Artur Shaik Date: Thu, 7 May 2015 12:40:51 +0600 Subject: [PATCH 13/36] Move JS snippet in external js file. --- qutebrowser/browser/webview.py | 76 +------------------- qutebrowser/javascript/position_caret.js | 89 ++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 74 deletions(-) create mode 100644 qutebrowser/javascript/position_caret.js diff --git a/qutebrowser/browser/webview.py b/qutebrowser/browser/webview.py index 2afd9dac3..5c592736e 100644 --- a/qutebrowser/browser/webview.py +++ b/qutebrowser/browser/webview.py @@ -181,79 +181,6 @@ class WebView(QWebView): self.load_status = val self.load_status_changed.emit(val.name) - def _position_caret(self): - """ - JS snippet to position caret at top of the screen. - Was borrowed from cVim source code - """ - self.page().currentFrame().evaluateJavaScript(""" - - function isElementInViewport(node) { - var i; - var boundingRect = node.getClientRects()[0] || node.getBoundingClientRect(); - if (boundingRect.width <= 1 && boundingRect.height <= 1) { - var rects = node.getClientRects(); - for (i = 0; i < rects.length; i++) { - if (rects[i].width > rects[0].height && rects[i].height > rects[0].height) { - boundingRect = rects[i]; - } - } - } - if (boundingRect === void 0) return null; - if (boundingRect.top > innerHeight || boundingRect.left > innerWidth) { - return null; - } - if (boundingRect.width <= 1 || boundingRect.height <= 1) { - var children = node.children; - var visibleChildNode = false; - for (i = 0, l = children.length; i < l; ++i) { - boundingRect = children[i].getClientRects()[0] || children[i].getBoundingClientRect(); - if (boundingRect.width > 1 && boundingRect.height > 1) { - visibleChildNode = true; - break; - } - } - if (visibleChildNode === false) return null; - } - if (boundingRect.top + boundingRect.height < 10 || boundingRect.left + boundingRect.width < -10) { - return null; - } - var computedStyle = window.getComputedStyle(node, null); - if (computedStyle.visibility !== 'visible' || - computedStyle.display === 'none' || - node.hasAttribute('disabled') || - parseInt(computedStyle.width, '10') === 0 || - parseInt(computedStyle.height, '10') === 0) { - return null; - } - return boundingRect.top >= -20; - } - - var walker = document.createTreeWalker(document.body, 4, null, false); - var node; - var textNodes = []; - 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; - if (isElementInViewport(element.parentElement)) { - el = element; - break; - } - } - if (el !== undefined) { - var range = document.createRange(); - range.setStart(el, 0); - range.setEnd(el, 0); - var sel = window.getSelection(); - sel.removeAllRanges(); - sel.addRange(range); - } - """) - @pyqtSlot(str, str) def on_config_changed(self, section, option): """Reinitialize the zoom neighborlist if related config changed.""" @@ -519,7 +446,8 @@ class WebView(QWebView): self.clearFocus() self.setFocus(Qt.OtherFocusReason) - self._position_caret() + self.page().currentFrame().evaluateJavaScript( + utils.read_file('javascript/position_caret.js')) @pyqtSlot(usertypes.KeyMode) def on_mode_left(self, mode): diff --git a/qutebrowser/javascript/position_caret.js b/qutebrowser/javascript/position_caret.js new file mode 100644 index 000000000..a31d83c55 --- /dev/null +++ b/qutebrowser/javascript/position_caret.js @@ -0,0 +1,89 @@ +/** +* Copyright 2014-2015 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 . +*/ + +/** + * Snippet to position caret at top of the page when caret mode enabled. + * Some code was borrowed from https://github.com/1995eaton/chromium-vim/blob/master/content_scripts/dom.js + * and https://github.com/1995eaton/chromium-vim/blob/master/content_scripts/visual.js + */ + +function isElementInViewport(node) { + var i; + var boundingRect = node.getClientRects()[0] || node.getBoundingClientRect(); + if (boundingRect.width <= 1 && boundingRect.height <= 1) { + var rects = node.getClientRects(); + for (i = 0; i < rects.length; i++) { + if (rects[i].width > rects[0].height && rects[i].height > rects[0].height) { + boundingRect = rects[i]; + } + } + } + if (boundingRect === void 0) return null; + if (boundingRect.top > innerHeight || boundingRect.left > innerWidth) { + return null; + } + if (boundingRect.width <= 1 || boundingRect.height <= 1) { + var children = node.children; + var visibleChildNode = false; + for (i = 0, l = children.length; i < l; ++i) { + boundingRect = children[i].getClientRects()[0] || children[i].getBoundingClientRect(); + if (boundingRect.width > 1 && boundingRect.height > 1) { + visibleChildNode = true; + break; + } + } + if (visibleChildNode === false) return null; + } + if (boundingRect.top + boundingRect.height < 10 || boundingRect.left + boundingRect.width < -10) { + return null; + } + var computedStyle = window.getComputedStyle(node, null); + if (computedStyle.visibility !== 'visible' || + computedStyle.display === 'none' || + node.hasAttribute('disabled') || + parseInt(computedStyle.width, '10') === 0 || + parseInt(computedStyle.height, '10') === 0) { + return null; + } + return boundingRect.top >= -20; +} + +var walker = document.createTreeWalker(document.body, 4, null, false); +var node; +var textNodes = []; +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; + if (isElementInViewport(element.parentElement)) { + el = element; + break; + } +} +if (el !== undefined) { + var range = document.createRange(); + range.setStart(el, 0); + range.setEnd(el, 0); + var sel = window.getSelection(); + sel.removeAllRanges(); + sel.addRange(range); +} From d3c6ebcf15ceb47768b6aa0bdd9d6a8e40da5a07 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 11 May 2015 20:21:01 +0200 Subject: [PATCH 14/36] Rename caret_selection to caret-selection. --- qutebrowser/config/configdata.py | 2 +- qutebrowser/mainwindow/statusbar/bar.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index e7da24caf..19e1e4da4 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -820,7 +820,7 @@ def data(readonly=False): SettingValue(typ.QssColor(), 'purple'), "Background color of the statusbar in caret mode."), - ('statusbar.bg.caret_selection', + ('statusbar.bg.caret-selection', SettingValue(typ.QssColor(), '#a12dff'), "Background color of the statusbar in caret selection enabled mode."), diff --git a/qutebrowser/mainwindow/statusbar/bar.py b/qutebrowser/mainwindow/statusbar/bar.py index e8311a1e7..aa655f671 100644 --- a/qutebrowser/mainwindow/statusbar/bar.py +++ b/qutebrowser/mainwindow/statusbar/bar.py @@ -108,7 +108,7 @@ class StatusBar(QWidget): } QWidget#StatusBar[caret_selection_active="true"] { - {{ color['statusbar.bg.caret_selection'] }} + {{ color['statusbar.bg.caret-selection'] }} } QWidget#StatusBar[prompt_active="true"] { From a36c0fcd4cd93c379359c9bf0718e13d793b11d3 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 11 May 2015 20:32:27 +0200 Subject: [PATCH 15/36] Fix lint. --- qutebrowser/browser/commands.py | 31 ++++++++++++++++--------- qutebrowser/browser/webview.py | 11 +++++---- qutebrowser/config/configdata.py | 3 ++- qutebrowser/keyinput/modeparsers.py | 1 - qutebrowser/mainwindow/statusbar/bar.py | 5 ++-- 5 files changed, 31 insertions(+), 20 deletions(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 65bac7901..a8ea029b9 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -42,7 +42,6 @@ from qutebrowser.utils import (message, usertypes, log, qtutils, urlutils, objreg, utils) from qutebrowser.utils.usertypes import KeyMode from qutebrowser.misc import editor -from qutebrowser.keyinput import modeman class CommandDispatcher: @@ -1267,9 +1266,11 @@ class CommandDispatcher: """Move the cursor or select to the start of next block.""" webview = self._current_widget() if not webview.selection_enabled: - act = [QWebPage.MoveToEndOfBlock, QWebPage.MoveToNextLine, QWebPage.MoveToStartOfBlock] + act = [QWebPage.MoveToEndOfBlock, QWebPage.MoveToNextLine, + QWebPage.MoveToStartOfBlock] else: - act = [QWebPage.SelectEndOfBlock, QWebPage.SelectNextLine, QWebPage.SelectStartOfBlock] + act = [QWebPage.SelectEndOfBlock, QWebPage.SelectNextLine, + QWebPage.SelectStartOfBlock] for _ in range(count): for a in act: webview.triggerPageAction(a) @@ -1280,9 +1281,11 @@ class CommandDispatcher: """Move the cursor or select to the start of previous block.""" webview = self._current_widget() if not webview.selection_enabled: - act = [QWebPage.MoveToStartOfBlock, QWebPage.MoveToPreviousLine, QWebPage.MoveToStartOfBlock] + act = [QWebPage.MoveToStartOfBlock, QWebPage.MoveToPreviousLine, + QWebPage.MoveToStartOfBlock] else: - act = [QWebPage.SelectStartOfBlock, QWebPage.SelectPreviousLine, QWebPage.SelectStartOfBlock] + act = [QWebPage.SelectStartOfBlock, QWebPage.SelectPreviousLine, + QWebPage.SelectStartOfBlock] for _ in range(count): for a in act: webview.triggerPageAction(a) @@ -1293,9 +1296,11 @@ class CommandDispatcher: """Move the cursor or select to the end of next block.""" webview = self._current_widget() if not webview.selection_enabled: - act = [QWebPage.MoveToEndOfBlock, QWebPage.MoveToNextLine, QWebPage.MoveToEndOfBlock] + act = [QWebPage.MoveToEndOfBlock, QWebPage.MoveToNextLine, + QWebPage.MoveToEndOfBlock] else: - act = [QWebPage.SelectEndOfBlock, QWebPage.SelectNextLine, QWebPage.SelectEndOfBlock] + act = [QWebPage.SelectEndOfBlock, QWebPage.SelectNextLine, + QWebPage.SelectEndOfBlock] for _ in range(count): for a in act: webview.triggerPageAction(a) @@ -1306,9 +1311,11 @@ class CommandDispatcher: """Move the cursor or select to the end of previous block.""" webview = self._current_widget() if not webview.selection_enabled: - act = [QWebPage.MoveToStartOfBlock, QWebPage.MoveToPreviousLine, QWebPage.MoveToEndOfBlock] + act = [QWebPage.MoveToStartOfBlock, QWebPage.MoveToPreviousLine, + QWebPage.MoveToEndOfBlock] else: - act = [QWebPage.SelectStartOfBlock, QWebPage.SelectPreviousLine, QWebPage.SelectEndOfBlock] + act = [QWebPage.SelectStartOfBlock, QWebPage.SelectPreviousLine, + QWebPage.SelectEndOfBlock] for _ in range(count): for a in act: webview.triggerPageAction(a) @@ -1364,8 +1371,10 @@ class CommandDispatcher: modes=[KeyMode.caret], scope='window') def toggle_selection(self): """Toggle caret selection mode.""" - self._current_widget().selection_enabled = not self._current_widget().selection_enabled - mainwindow = objreg.get('main-window', scope='window', window=self._win_id) + widget = self._current_widget() + widget.selection_enabled = not widget.selection_enabled + mainwindow = objreg.get('main-window', scope='window', + window=self._win_id) mainwindow.status.on_mode_entered(usertypes.KeyMode.caret) @cmdutils.register(instance='command-dispatcher', hide=True, diff --git a/qutebrowser/browser/webview.py b/qutebrowser/browser/webview.py index 5c592736e..684ce8e3d 100644 --- a/qutebrowser/browser/webview.py +++ b/qutebrowser/browser/webview.py @@ -106,7 +106,7 @@ class WebView(QWebView): self.keep_icon = False self.search_text = None self.search_flags = 0 - self.selection_enabled = False; + self.selection_enabled = False self.init_neighborlist() cfg = objreg.get('config') cfg.changed.connect(self.init_neighborlist) @@ -441,13 +441,14 @@ class WebView(QWebView): settings.setAttribute(QWebSettings.CaretBrowsingEnabled, True) self.selection_enabled = False - tabbed = objreg.get('tabbed-browser', scope='window', window=self.win_id) - if tabbed.currentWidget().tab_id == self.tab_id: + tabbed_browser = objreg.get('tabbed-browser', scope='window', + window=self.win_id) + if tabbed_browser.currentWidget().tab_id == self.tab_id: self.clearFocus() self.setFocus(Qt.OtherFocusReason) self.page().currentFrame().evaluateJavaScript( - utils.read_file('javascript/position_caret.js')) + utils.read_file('javascript/position_caret.js')) @pyqtSlot(usertypes.KeyMode) def on_mode_left(self, mode): @@ -463,7 +464,7 @@ class WebView(QWebView): # Remove selection if exist self.triggerPageAction(QWebPage.MoveToNextChar) settings.setAttribute(QWebSettings.CaretBrowsingEnabled, False) - self.selection_enabled = False; + self.selection_enabled = False self.setFocusPolicy(Qt.WheelFocus) diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index 19e1e4da4..ddde678d0 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -822,7 +822,8 @@ def data(readonly=False): ('statusbar.bg.caret-selection', SettingValue(typ.QssColor(), '#a12dff'), - "Background color of the statusbar in caret selection enabled mode."), + "Background color of the statusbar in caret mode with a " + "selection"), ('statusbar.progress.bg', SettingValue(typ.QssColor(), 'white'), diff --git a/qutebrowser/keyinput/modeparsers.py b/qutebrowser/keyinput/modeparsers.py index 8ccdf6ad3..8a6165823 100644 --- a/qutebrowser/keyinput/modeparsers.py +++ b/qutebrowser/keyinput/modeparsers.py @@ -231,4 +231,3 @@ class CaretKeyParser(keyparser.CommandKeyParser): def __repr__(self): return utils.get_repr(self) - diff --git a/qutebrowser/mainwindow/statusbar/bar.py b/qutebrowser/mainwindow/statusbar/bar.py index aa655f671..503cc38c1 100644 --- a/qutebrowser/mainwindow/statusbar/bar.py +++ b/qutebrowser/mainwindow/statusbar/bar.py @@ -270,7 +270,7 @@ class StatusBar(QWidget): @pyqtProperty(bool) def caret_selection_active(self): - """Getter for self.caret_selection_active, so it can be used as Qt property.""" + """Getter for caret_selection_active, so it can be used as property.""" return self._caret_selection_active def _set_mode_active(self, mode, val): @@ -284,7 +284,8 @@ class StatusBar(QWidget): self._insert_active = val elif mode == usertypes.KeyMode.caret: log.statusbar.debug("Setting caret_active to {}".format(val)) - webview = objreg.get("tabbed-browser", scope="window", window=self._win_id).currentWidget() + webview = objreg.get("tabbed-browser", scope="window", + window=self._win_id).currentWidget() if val and webview.selection_enabled: self._set_mode_text("{} selection".format(mode.name)) self._caret_selection_active = val From 37050c49fc01417778af511d683acca08d8ccbfa Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 11 May 2015 20:33:16 +0200 Subject: [PATCH 16/36] Include .js files in MANIFEST. --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index eabdf977b..9db0cdca0 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,6 +2,7 @@ global-exclude __pycache__ *.pyc *.pyo recursive-include qutebrowser/html *.html recursive-include qutebrowser/test *.py +recursive-include qutebrowser/javascript *.js graft icons graft scripts/pylint_checkers graft doc/img From 1f94e0fee63e67d2bba818ef8d64ea9b5779247e Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 11 May 2015 20:33:42 +0200 Subject: [PATCH 17/36] js: Remove obsolete argument to createTreeWalker. "createNodeIterator() and createTreeWalker() now have optional arguments and lack a fourth argument which is no longer relevant given entity references never made it into the DOM." --- qutebrowser/javascript/position_caret.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/javascript/position_caret.js b/qutebrowser/javascript/position_caret.js index a31d83c55..9e14b5b3c 100644 --- a/qutebrowser/javascript/position_caret.js +++ b/qutebrowser/javascript/position_caret.js @@ -64,7 +64,7 @@ function isElementInViewport(node) { return boundingRect.top >= -20; } -var walker = document.createTreeWalker(document.body, 4, null, false); +var walker = document.createTreeWalker(document.body, 4, null); var node; var textNodes = []; while (node = walker.nextNode()) { From 756aa3e16f0cc7fe5d7dbfcffef78b936a27ca75 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 11 May 2015 21:10:18 +0200 Subject: [PATCH 18/36] Fix tests because of new '0' key handling. --- tests/keyinput/test_basekeyparser.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/tests/keyinput/test_basekeyparser.py b/tests/keyinput/test_basekeyparser.py index 29d556ac5..d7f7b20a3 100644 --- a/tests/keyinput/test_basekeyparser.py +++ b/tests/keyinput/test_basekeyparser.py @@ -38,7 +38,8 @@ BINDINGS = {'test': {'': 'ctrla', 'a': 'a', 'ba': 'ba', 'ax': 'ax', - 'ccc': 'ccc'}, + 'ccc': 'ccc', + '0': '0'}, 'test2': {'foo': 'bar', '': 'ctrlx'}} @@ -189,6 +190,12 @@ class TestKeyChain: self.kp.execute.assert_called_once_with('ba', self.kp.Type.chain, None) assert self.kp._keystring == '' + def test_0(self, fake_keyevent_factory): + """Test with 0 keypress.""" + self.kp.handle(fake_keyevent_factory(Qt.Key_0, text='0')) + self.kp.execute.assert_called_once_with('0', self.kp.Type.chain, None) + assert self.kp._keystring == '' + def test_ambiguous_keychain(self, fake_keyevent_factory, mocker, stubs): """Test ambiguous keychain.""" mocker.patch('qutebrowser.keyinput.basekeyparser.config', @@ -240,7 +247,9 @@ class TestCount: self.kp.handle(fake_keyevent_factory(Qt.Key_0, text='0')) self.kp.handle(fake_keyevent_factory(Qt.Key_B, text='b')) self.kp.handle(fake_keyevent_factory(Qt.Key_A, text='a')) - self.kp.execute.assert_called_once_with('ba', self.kp.Type.chain, 0) + calls = [mock.call('0', self.kp.Type.chain, None), + mock.call('ba', self.kp.Type.chain, None)] + self.kp.execute.assert_has_calls(calls) assert self.kp._keystring == '' def test_count_42(self, fake_keyevent_factory): From 418934644b9d4816fad1114b8106d31bc766ed5a Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 11 May 2015 22:29:44 +0200 Subject: [PATCH 19/36] Improve docstrings. --- qutebrowser/browser/commands.py | 73 ++++++++++++++++++++++++++------- 1 file changed, 59 insertions(+), 14 deletions(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index a8ea029b9..6f0abb3ad 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1154,7 +1154,11 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', hide=True, modes=[KeyMode.caret], scope='window', count='count') def move_to_next_line(self, count=1): - """Move the cursor or select to the next line.""" + """Move the cursor or selection to the next line. + + Args: + count: How many lines to move. + """ webview = self._current_widget() if not webview.selection_enabled: act = QWebPage.MoveToNextLine @@ -1166,7 +1170,11 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', hide=True, modes=[KeyMode.caret], scope='window', count='count') def move_to_prev_line(self, count=1): - """Move the cursor or select to the prev line.""" + """Move the cursor or selection to the prev line. + + Args: + count: How many lines to move. + """ webview = self._current_widget() if not webview.selection_enabled: act = QWebPage.MoveToPreviousLine @@ -1178,7 +1186,11 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', hide=True, modes=[KeyMode.caret], scope='window', count='count') def move_to_next_char(self, count=1): - """Move the cursor or select to the next char.""" + """Move the cursor or selection to the next char. + + Args: + count: How many lines to move. + """ webview = self._current_widget() if not webview.selection_enabled: act = QWebPage.MoveToNextChar @@ -1190,7 +1202,11 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', hide=True, modes=[KeyMode.caret], scope='window', count='count') def move_to_prev_char(self, count=1): - """Move the cursor or select to the prev char.""" + """Move the cursor or selection to the previous char. + + Args: + count: How many chars to move. + """ webview = self._current_widget() if not webview.selection_enabled: act = QWebPage.MoveToPreviousChar @@ -1202,7 +1218,11 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', hide=True, modes=[KeyMode.caret], scope='window', count='count') def move_to_end_of_word(self, count=1): - """Move the cursor or select to the next word.""" + """Move the cursor or selection to the end of the word. + + Args: + count: How many words to move. + """ webview = self._current_widget() if not webview.selection_enabled: act = QWebPage.MoveToNextWord @@ -1214,7 +1234,11 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', hide=True, modes=[KeyMode.caret], scope='window', count='count') def move_to_next_word(self, count=1): - """Move the cursor or select to the next word.""" + """Move the cursor or selection to the next word. + + Args: + count: How many words to move. + """ webview = self._current_widget() if not webview.selection_enabled: act = [QWebPage.MoveToNextWord, QWebPage.MoveToNextChar] @@ -1227,7 +1251,11 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', hide=True, modes=[KeyMode.caret], scope='window', count='count') def move_to_prev_word(self, count=1): - """Move the cursor or select to the prev word.""" + """Move the cursor or selection to the previous word. + + Args: + count: How many words to move. + """ webview = self._current_widget() if not webview.selection_enabled: act = QWebPage.MoveToPreviousWord @@ -1263,7 +1291,11 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', hide=True, modes=[KeyMode.caret], scope='window', count='count') def move_to_start_of_next_block(self, count=1): - """Move the cursor or select to the start of next block.""" + """Move the cursor or selection to the start of next block. + + Args: + count: How many blocks to move. + """ webview = self._current_widget() if not webview.selection_enabled: act = [QWebPage.MoveToEndOfBlock, QWebPage.MoveToNextLine, @@ -1278,7 +1310,11 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', hide=True, modes=[KeyMode.caret], scope='window', count='count') def move_to_start_of_prev_block(self, count=1): - """Move the cursor or select to the start of previous block.""" + """Move the cursor or selection to the start of previous block. + + Args: + count: How many blocks to move. + """ webview = self._current_widget() if not webview.selection_enabled: act = [QWebPage.MoveToStartOfBlock, QWebPage.MoveToPreviousLine, @@ -1293,7 +1329,11 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', hide=True, modes=[KeyMode.caret], scope='window', count='count') def move_to_end_of_next_block(self, count=1): - """Move the cursor or select to the end of next block.""" + """Move the cursor or selection to the end of next block. + + Args: + count: How many blocks to move. + """ webview = self._current_widget() if not webview.selection_enabled: act = [QWebPage.MoveToEndOfBlock, QWebPage.MoveToNextLine, @@ -1308,7 +1348,11 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', hide=True, modes=[KeyMode.caret], scope='window', count='count') def move_to_end_of_prev_block(self, count=1): - """Move the cursor or select to the end of previous block.""" + """Move the cursor or selection to the end of previous block. + + Args: + count: How many blocks to move. + """ webview = self._current_widget() if not webview.selection_enabled: act = [QWebPage.MoveToStartOfBlock, QWebPage.MoveToPreviousLine, @@ -1323,7 +1367,8 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', hide=True, modes=[KeyMode.caret], scope='window') def move_to_start_of_document(self): - """Move the cursor or select to the start of document.""" + """Move the cursor or selection to the start of the document.""" + webview = self._current_widget() if not webview.selection_enabled: act = QWebPage.MoveToStartOfDocument @@ -1334,7 +1379,7 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', hide=True, modes=[KeyMode.caret], scope='window') def move_to_end_of_document(self): - """Move the cursor or select to the end of document.""" + """Move the cursor or selection to the end of the document.""" webview = self._current_widget() if not webview.selection_enabled: act = QWebPage.MoveToEndOfDocument @@ -1345,7 +1390,7 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', hide=True, modes=[KeyMode.caret], scope='window') def yank_selected(self, sel=False): - """Yank selected text to the clipboard or primary selection. + """Yank the selected text to the clipboard or primary selection. Args: sel: Use the primary selection instead of the clipboard. From f36a7444d72d038666f9c55bcaa01121c4aa9eb8 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 12 May 2015 06:53:13 +0200 Subject: [PATCH 20/36] js: Add .eslintrc. --- .eslintrc | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 .eslintrc diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 000000000..dcd5b3b8c --- /dev/null +++ b/.eslintrc @@ -0,0 +1,47 @@ +# vim: ft=yaml + +env: + browser: true + +rules: + block-scoped-var: 2 + complexity: [2, 5] + dot-location: 2 + default-case: 2 + guard-for-in: 2 + no-div-regex: 2 + no-param-reassign: 2 + no-eq-null: 2 + no-floating-decimal: 2 + no-self-compare: 2 + no-throw-literal: 2 + no-void: 2 + radix: 2 + wrap-iife: 2 + brace-style: [2, "1tbs", {"allowSingleLine": true}] + comma-style: [2, "last"] + consistent-this: [2, "self"] + func-names: 2 + func-style: [2, "declaration"] + indent: [2, 4, {"indentSwitchCase": true}] + linebreak-style: [2, "unix"] + max-nested-callbacks: [2, 3] + no-lonely-if: 2 + no-multiple-empty-lines: [2, {"max": 2}] + no-nested-ternary: 2 + no-unneeded-ternary: 2 + operator-assignment: [2, "always"] + operator-linebreak: [2, "after"] + space-after-keywords: [2, "always"] + space-before-blocks: [2, "always"] + space-before-function-paren: [2, {"anonymous": "always", "named": "never"}] + space-in-brackets: [2, "never"] + space-in-parens: [2, "never"] + space-unary-ops: [2, {"words": true, "nonwords": false}] + spaced-line-comment: [2, "always"] + max-depth: [2, 5] + max-len: [2, 79, 4] + max-params: [2, 5] + max-statements: [2, 20] + no-bitwise: 2 + no-reserved-keys: 2 From 7a67af24f0a988dbf1d3acbb79abf030834c9bdf Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 12 May 2015 07:06:55 +0200 Subject: [PATCH 21/36] js: Fix some lint. --- .eslintrc | 4 +-- qutebrowser/javascript/position_caret.js | 46 ++++++++++++++++-------- 2 files changed, 34 insertions(+), 16 deletions(-) diff --git a/.eslintrc b/.eslintrc index dcd5b3b8c..2eec935a7 100644 --- a/.eslintrc +++ b/.eslintrc @@ -5,7 +5,6 @@ env: rules: block-scoped-var: 2 - complexity: [2, 5] dot-location: 2 default-case: 2 guard-for-in: 2 @@ -42,6 +41,7 @@ rules: max-depth: [2, 5] max-len: [2, 79, 4] max-params: [2, 5] - max-statements: [2, 20] + max-statements: [2, 30] no-bitwise: 2 no-reserved-keys: 2 + global-strict: 0 diff --git a/qutebrowser/javascript/position_caret.js b/qutebrowser/javascript/position_caret.js index 9e14b5b3c..36bf0451f 100644 --- a/qutebrowser/javascript/position_caret.js +++ b/qutebrowser/javascript/position_caret.js @@ -1,40 +1,53 @@ /** -* Copyright 2014-2015 Florian Bruhin (The Compiler) -* +* Copyright 2015 Artur Shaik +* Copyright 2015 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 . */ +/* eslint-disable max-len */ + /** - * Snippet to position caret at top of the page when caret mode enabled. - * Some code was borrowed from https://github.com/1995eaton/chromium-vim/blob/master/content_scripts/dom.js - * and https://github.com/1995eaton/chromium-vim/blob/master/content_scripts/visual.js + * Snippet to position caret at top of the page when caret mode is enabled. + * Some code was borrowed from: + * + * https://github.com/1995eaton/chromium-vim/blob/master/content_scripts/dom.js + * https://github.com/1995eaton/chromium-vim/blob/master/content_scripts/visual.js */ +/* eslint-enable max-len */ + +"use strict"; + function isElementInViewport(node) { var i; - var boundingRect = node.getClientRects()[0] || node.getBoundingClientRect(); + var boundingRect = (node.getClientRects()[0] || + node.getBoundingClientRect()); if (boundingRect.width <= 1 && boundingRect.height <= 1) { var rects = node.getClientRects(); for (i = 0; i < rects.length; i++) { - if (rects[i].width > rects[0].height && rects[i].height > rects[0].height) { + if (rects[i].width > rects[0].height && + rects[i].height > rects[0].height) { boundingRect = rects[i]; } } } - if (boundingRect === void 0) return null; + if (boundingRect === undefined) { + return null; + } if (boundingRect.top > innerHeight || boundingRect.left > innerWidth) { return null; } @@ -42,15 +55,19 @@ function isElementInViewport(node) { var children = node.children; var visibleChildNode = false; for (i = 0, l = children.length; i < l; ++i) { - boundingRect = children[i].getClientRects()[0] || children[i].getBoundingClientRect(); + boundingRect = (children[i].getClientRects()[0] || + children[i].getBoundingClientRect()); if (boundingRect.width > 1 && boundingRect.height > 1) { visibleChildNode = true; break; } } - if (visibleChildNode === false) return null; + if (visibleChildNode === false) { + return null; + } } - if (boundingRect.top + boundingRect.height < 10 || boundingRect.left + boundingRect.width < -10) { + if (boundingRect.top + boundingRect.height < 10 || + boundingRect.left + boundingRect.width < -10) { return null; } var computedStyle = window.getComputedStyle(node, null); @@ -64,6 +81,7 @@ function isElementInViewport(node) { return boundingRect.top >= -20; } + var walker = document.createTreeWalker(document.body, 4, null); var node; var textNodes = []; From 3f21ac6b6a1ab367f79c78b979ae392986d7af0c Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 12 May 2015 07:10:08 +0200 Subject: [PATCH 22/36] js: Use an IIFE. --- .eslintrc | 2 +- qutebrowser/javascript/position_caret.js | 45 ++++++++++++------------ 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/.eslintrc b/.eslintrc index 2eec935a7..ab23d427e 100644 --- a/.eslintrc +++ b/.eslintrc @@ -16,7 +16,7 @@ rules: no-throw-literal: 2 no-void: 2 radix: 2 - wrap-iife: 2 + wrap-iife: [2, "inside"] brace-style: [2, "1tbs", {"allowSingleLine": true}] comma-style: [2, "last"] consistent-this: [2, "self"] diff --git a/qutebrowser/javascript/position_caret.js b/qutebrowser/javascript/position_caret.js index 36bf0451f..62e610245 100644 --- a/qutebrowser/javascript/position_caret.js +++ b/qutebrowser/javascript/position_caret.js @@ -81,27 +81,28 @@ function isElementInViewport(node) { return boundingRect.top >= -20; } - -var walker = document.createTreeWalker(document.body, 4, null); -var node; -var textNodes = []; -while (node = walker.nextNode()) { - if (node.nodeType === 3 && node.data.trim() !== '') { - textNodes.push(node); +(function() { + var walker = document.createTreeWalker(document.body, 4, null); + var node; + var textNodes = []; + 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; - if (isElementInViewport(element.parentElement)) { - el = element; - break; + for (var i = 0; i < textNodes.length; i++) { + var element = textNodes[i].parentElement; + if (isElementInViewport(element.parentElement)) { + el = element; + break; + } } -} -if (el !== undefined) { - var range = document.createRange(); - range.setStart(el, 0); - range.setEnd(el, 0); - var sel = window.getSelection(); - sel.removeAllRanges(); - sel.addRange(range); -} + if (el !== undefined) { + var range = document.createRange(); + range.setStart(el, 0); + range.setEnd(el, 0); + var sel = window.getSelection(); + sel.removeAllRanges(); + sel.addRange(range); + } +})(); From d1e88c5e8db02ee3a66d0d4b51cc231a0a04c8e7 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 12 May 2015 07:11:45 +0200 Subject: [PATCH 23/36] js: Add 'var'. --- qutebrowser/javascript/position_caret.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/qutebrowser/javascript/position_caret.js b/qutebrowser/javascript/position_caret.js index 62e610245..d3cdbf43a 100644 --- a/qutebrowser/javascript/position_caret.js +++ b/qutebrowser/javascript/position_caret.js @@ -54,7 +54,8 @@ function isElementInViewport(node) { if (boundingRect.width <= 1 || boundingRect.height <= 1) { var children = node.children; var visibleChildNode = false; - for (i = 0, l = children.length; i < l; ++i) { + var l = children.length; + for (i = 0; i < l; ++i) { boundingRect = (children[i].getClientRects()[0] || children[i].getBoundingClientRect()); if (boundingRect.width > 1 && boundingRect.height > 1) { @@ -85,6 +86,7 @@ function isElementInViewport(node) { var walker = document.createTreeWalker(document.body, 4, null); var node; var textNodes = []; + var el; while (node = walker.nextNode()) { if (node.nodeType === 3 && node.data.trim() !== '') { textNodes.push(node); From 28ec7b4698e49e430a54406876451374618701ac Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 12 May 2015 07:14:10 +0200 Subject: [PATCH 24/36] js: Fix radix parameters. --- qutebrowser/javascript/position_caret.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qutebrowser/javascript/position_caret.js b/qutebrowser/javascript/position_caret.js index d3cdbf43a..a69156713 100644 --- a/qutebrowser/javascript/position_caret.js +++ b/qutebrowser/javascript/position_caret.js @@ -75,8 +75,8 @@ function isElementInViewport(node) { if (computedStyle.visibility !== 'visible' || computedStyle.display === 'none' || node.hasAttribute('disabled') || - parseInt(computedStyle.width, '10') === 0 || - parseInt(computedStyle.height, '10') === 0) { + parseInt(computedStyle.width, 10) === 0 || + parseInt(computedStyle.height, 10) === 0) { return null; } return boundingRect.top >= -20; From a96065861718eb887bab498992253dcad44fe4b2 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 12 May 2015 07:15:37 +0200 Subject: [PATCH 25/36] js: Fix more lint. --- .eslintrc | 4 ++-- qutebrowser/javascript/position_caret.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.eslintrc b/.eslintrc index ab23d427e..bef2e9c1a 100644 --- a/.eslintrc +++ b/.eslintrc @@ -20,7 +20,6 @@ rules: brace-style: [2, "1tbs", {"allowSingleLine": true}] comma-style: [2, "last"] consistent-this: [2, "self"] - func-names: 2 func-style: [2, "declaration"] indent: [2, 4, {"indentSwitchCase": true}] linebreak-style: [2, "unix"] @@ -33,7 +32,7 @@ rules: operator-linebreak: [2, "after"] space-after-keywords: [2, "always"] space-before-blocks: [2, "always"] - space-before-function-paren: [2, {"anonymous": "always", "named": "never"}] + space-before-function-paren: [2, {"anonymous": "never", "named": "never"}] space-in-brackets: [2, "never"] space-in-parens: [2, "never"] space-unary-ops: [2, {"words": true, "nonwords": false}] @@ -45,3 +44,4 @@ rules: no-bitwise: 2 no-reserved-keys: 2 global-strict: 0 + quotes: 0 diff --git a/qutebrowser/javascript/position_caret.js b/qutebrowser/javascript/position_caret.js index a69156713..5ffd882de 100644 --- a/qutebrowser/javascript/position_caret.js +++ b/qutebrowser/javascript/position_caret.js @@ -87,7 +87,7 @@ function isElementInViewport(node) { var node; var textNodes = []; var el; - while (node = walker.nextNode()) { + while ((node = walker.nextNode())) { if (node.nodeType === 3 && node.data.trim() !== '') { textNodes.push(node); } From 54ae6a63eedf292dec9b602194d005196ac7a90a Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 12 May 2015 17:58:53 +0200 Subject: [PATCH 26/36] Fix lint. --- MANIFEST.in | 1 + qutebrowser/browser/commands.py | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index 9db0cdca0..7ecd44de2 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -30,4 +30,5 @@ exclude qutebrowser.rcc exclude .coveragerc exclude .flake8 exclude .pylintrc +exclude .eslintrc exclude doc/help diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 6f0abb3ad..0fcc91268 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1368,7 +1368,6 @@ class CommandDispatcher: modes=[KeyMode.caret], scope='window') def move_to_start_of_document(self): """Move the cursor or selection to the start of the document.""" - webview = self._current_widget() if not webview.selection_enabled: act = QWebPage.MoveToStartOfDocument From dd0e230a321b4cda567a0528df71c2dbf153dfbe Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 13 May 2015 07:01:48 +0200 Subject: [PATCH 27/36] Re-add v keybinding for toggle-selection. See #653. --- qutebrowser/config/configdata.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index ddde678d0..1020493c2 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -1261,7 +1261,7 @@ KEY_DATA = collections.OrderedDict([ ])), ('caret', collections.OrderedDict([ - ('toggle-selection', ['']), + ('toggle-selection', ['v', '']), ('drop-selection', ['']), ('enter-mode normal', ['c']), ('move-to-next-line', ['j']), From ce1b82616d8da873889898de5bc5f347e8de8dd6 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 13 May 2015 07:17:05 +0200 Subject: [PATCH 28/36] Fix spelling. --- qutebrowser/browser/webview.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/browser/webview.py b/qutebrowser/browser/webview.py index 684ce8e3d..b26045c55 100644 --- a/qutebrowser/browser/webview.py +++ b/qutebrowser/browser/webview.py @@ -461,7 +461,7 @@ class WebView(QWebView): settings = self.settings() if settings.testAttribute(QWebSettings.CaretBrowsingEnabled): if self.selection_enabled and self.hasSelection(): - # Remove selection if exist + # Remove selection if it exists self.triggerPageAction(QWebPage.MoveToNextChar) settings.setAttribute(QWebSettings.CaretBrowsingEnabled, False) self.selection_enabled = False From 88fc1864021aa75188a961194e5debace2c2f3ba Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 13 May 2015 07:29:17 +0200 Subject: [PATCH 29/36] Add tmux-like Enter binding. See #653. --- qutebrowser/browser/commands.py | 6 +++++- qutebrowser/config/configdata.py | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 0fcc91268..e5f646dcb 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -38,6 +38,7 @@ import pygments.formatters from qutebrowser.commands import userscripts, cmdexc, cmdutils from qutebrowser.config import config, configexc from qutebrowser.browser import webelem, inspector +from qutebrowser.keyinput import modeman from qutebrowser.utils import (message, usertypes, log, qtutils, urlutils, objreg, utils) from qutebrowser.utils.usertypes import KeyMode @@ -1388,11 +1389,12 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', hide=True, modes=[KeyMode.caret], scope='window') - def yank_selected(self, sel=False): + def yank_selected(self, sel=False, leave=False): """Yank the selected text to the clipboard or primary selection. Args: sel: Use the primary selection instead of the clipboard. + leave: If given, leave visual mode after yanking. """ s = self._current_widget().selectedText() if not self._current_widget().hasSelection() or len(s) == 0: @@ -1410,6 +1412,8 @@ class CommandDispatcher: clipboard.setText(s, mode) message.info(self._win_id, "{} {} yanked to {}".format( len(s), "char" if len(s) == 1 else "chars", target)) + if leave: + modeman.leave(self._win_id, KeyMode.caret, "yank selected") @cmdutils.register(instance='command-dispatcher', hide=True, modes=[KeyMode.caret], scope='window') diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index 1020493c2..6fe88b5e1 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -1281,6 +1281,7 @@ KEY_DATA = collections.OrderedDict([ ('move-to-end-of-document', ['G']), ('yank-selected', ['y']), ('yank-selected -p', ['Y']), + ('yank-selected --leave', ['', '']), ('scroll -50 0', ['H']), ('scroll 0 50', ['J']), ('scroll 0 -50', ['K']), From 866b299fef8e02a33a992290489cfdb1b0ae6ed9 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 13 May 2015 07:54:06 +0200 Subject: [PATCH 30/36] Fix adding of new default section to keyconf. When trying to add a new binding with multiple values, the bindings were added immediately and the next _is_new() check returned False because the command was already bound. With this change, the new bindings first get added to a temporary dict so _is_new() returns the correct result. See #653. --- qutebrowser/config/parsers/keyconf.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/qutebrowser/config/parsers/keyconf.py b/qutebrowser/config/parsers/keyconf.py index b15a9ae4a..059ea5cdc 100644 --- a/qutebrowser/config/parsers/keyconf.py +++ b/qutebrowser/config/parsers/keyconf.py @@ -237,20 +237,31 @@ class KeyConfigParser(QObject): only_new: If set, only keybindings which are completely unused (same command/key not bound) are added. """ + + # {'sectname': {'keychain1': 'command', 'keychain2': 'command'}, ...} + bindings_to_add = collections.OrderedDict() + for sectname, sect in configdata.KEY_DATA.items(): sectname = self._normalize_sectname(sectname) + bindings_to_add[sectname] = collections.OrderedDict() + for command, keychains in sect.items(): + for e in keychains: + if not only_new or self._is_new(sectname, command, e): + assert e not in bindings_to_add[sectname] + bindings_to_add[sectname][e] = command + + for sectname, sect in bindings_to_add.items(): if not sect: if not only_new: self.keybindings[sectname] = collections.OrderedDict() - self._mark_config_dirty() else: - for command, keychains in sect.items(): - for e in keychains: - if not only_new or self._is_new(sectname, command, e): - self._add_binding(sectname, e, command) - self._mark_config_dirty() + for keychain, command in sect.items(): + self._add_binding(sectname, keychain, command) self.changed.emit(sectname) + if bindings_to_add: + self._mark_config_dirty() + def _is_new(self, sectname, command, keychain): """Check if a given binding is new. From f59a14758980ec76779bf2957c9a71a9ac844912 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 13 May 2015 07:58:33 +0200 Subject: [PATCH 31/36] Leave mode when yanking by default. See #653. --- qutebrowser/browser/commands.py | 6 +++--- qutebrowser/config/configdata.py | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index e5f646dcb..3b71fc4f4 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1389,12 +1389,12 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', hide=True, modes=[KeyMode.caret], scope='window') - def yank_selected(self, sel=False, leave=False): + def yank_selected(self, sel=False, keep=False): """Yank the selected text to the clipboard or primary selection. Args: sel: Use the primary selection instead of the clipboard. - leave: If given, leave visual mode after yanking. + keep: If given, stay in visual mode after yanking. """ s = self._current_widget().selectedText() if not self._current_widget().hasSelection() or len(s) == 0: @@ -1412,7 +1412,7 @@ class CommandDispatcher: clipboard.setText(s, mode) message.info(self._win_id, "{} {} yanked to {}".format( len(s), "char" if len(s) == 1 else "chars", target)) - if leave: + if not keep: modeman.leave(self._win_id, KeyMode.caret, "yank selected") @cmdutils.register(instance='command-dispatcher', hide=True, diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index 6fe88b5e1..33be98db8 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -1279,9 +1279,8 @@ KEY_DATA = collections.OrderedDict([ ('move-to-end-of-line', ['$']), ('move-to-start-of-document', ['gg']), ('move-to-end-of-document', ['G']), - ('yank-selected', ['y']), ('yank-selected -p', ['Y']), - ('yank-selected --leave', ['', '']), + ('yank-selected', ['y', '', '']), ('scroll -50 0', ['H']), ('scroll 0 50', ['J']), ('scroll 0 -50', ['K']), From a728704cce335ee352707ba220c8c8fdc2902356 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 13 May 2015 21:52:42 +0200 Subject: [PATCH 32/36] toggle-selection cleanup --- qutebrowser/browser/commands.py | 2 +- qutebrowser/mainwindow/statusbar/bar.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 3b71fc4f4..4679eb9b8 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1423,7 +1423,7 @@ class CommandDispatcher: widget.selection_enabled = not widget.selection_enabled mainwindow = objreg.get('main-window', scope='window', window=self._win_id) - mainwindow.status.on_mode_entered(usertypes.KeyMode.caret) + mainwindow.status.set_mode_active(usertypes.KeyMode.caret, True) @cmdutils.register(instance='command-dispatcher', hide=True, modes=[KeyMode.caret], scope='window') diff --git a/qutebrowser/mainwindow/statusbar/bar.py b/qutebrowser/mainwindow/statusbar/bar.py index 503cc38c1..6df62e258 100644 --- a/qutebrowser/mainwindow/statusbar/bar.py +++ b/qutebrowser/mainwindow/statusbar/bar.py @@ -273,7 +273,7 @@ class StatusBar(QWidget): """Getter for caret_selection_active, so it can be used as property.""" return self._caret_selection_active - def _set_mode_active(self, mode, val): + def set_mode_active(self, mode, val): """Setter for self.{insert,caret}_active. Re-set the stylesheet after setting the value, so everything gets @@ -474,7 +474,7 @@ class StatusBar(QWidget): if mode in mode_manager.passthrough: self._set_mode_text(mode.name) if mode in (usertypes.KeyMode.insert, usertypes.KeyMode.caret): - self._set_mode_active(mode, True) + self.set_mode_active(mode, True) @pyqtSlot(usertypes.KeyMode, usertypes.KeyMode) def on_mode_left(self, old_mode, new_mode): @@ -487,7 +487,7 @@ class StatusBar(QWidget): else: self.txt.set_text(self.txt.Text.normal, '') if old_mode in (usertypes.KeyMode.insert, usertypes.KeyMode.caret): - self._set_mode_active(old_mode, False) + self.set_mode_active(old_mode, False) @config.change_filter('ui', 'message-timeout') def set_pop_timer_interval(self): From 222627b08da95298ed80a24706d60017742e5615 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 13 May 2015 22:25:21 +0200 Subject: [PATCH 33/36] Clean up caret initialisation. --- qutebrowser/browser/webview.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/qutebrowser/browser/webview.py b/qutebrowser/browser/webview.py index b26045c55..b2546d367 100644 --- a/qutebrowser/browser/webview.py +++ b/qutebrowser/browser/webview.py @@ -441,12 +441,11 @@ class WebView(QWebView): settings.setAttribute(QWebSettings.CaretBrowsingEnabled, True) self.selection_enabled = False - tabbed_browser = objreg.get('tabbed-browser', scope='window', - window=self.win_id) - if tabbed_browser.currentWidget().tab_id == self.tab_id: + if self.isVisible(): + # Sometimes the caret isn't immediately visible, but unfocusing + # and refocusing it fixes that. self.clearFocus() self.setFocus(Qt.OtherFocusReason) - self.page().currentFrame().evaluateJavaScript( utils.read_file('javascript/position_caret.js')) From bc54eb86713742de066c1a799919025050bd8cf1 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 13 May 2015 22:27:54 +0200 Subject: [PATCH 34/36] Make get_modeman private again. --- qutebrowser/keyinput/modeman.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qutebrowser/keyinput/modeman.py b/qutebrowser/keyinput/modeman.py index 3d4760849..fc70ac76b 100644 --- a/qutebrowser/keyinput/modeman.py +++ b/qutebrowser/keyinput/modeman.py @@ -97,25 +97,25 @@ def init(win_id, parent): return modeman -def get_modeman(win_id): +def _get_modeman(win_id): """Get a modemanager object.""" return objreg.get('mode-manager', scope='window', window=win_id) def enter(win_id, mode, reason=None, only_if_normal=False): """Enter the mode 'mode'.""" - get_modeman(win_id).enter(mode, reason, only_if_normal) + _get_modeman(win_id).enter(mode, reason, only_if_normal) def leave(win_id, mode, reason=None): """Leave the mode 'mode'.""" - get_modeman(win_id).leave(mode, reason) + _get_modeman(win_id).leave(mode, reason) def maybe_leave(win_id, mode, reason=None): """Convenience method to leave 'mode' without exceptions.""" try: - get_modeman(win_id).leave(mode, reason) + _get_modeman(win_id).leave(mode, reason) except NotInModeError as e: # This is rather likely to happen, so we only log to debug log. log.modes.debug("{} (leave reason: {})".format(e, reason)) From 947dcd556b864d3d37db05bef39c01962f911775 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 13 May 2015 22:29:21 +0200 Subject: [PATCH 35/36] Clean up CaretKeyParser. --- qutebrowser/keyinput/modeparsers.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/qutebrowser/keyinput/modeparsers.py b/qutebrowser/keyinput/modeparsers.py index 8a6165823..8d47de0c1 100644 --- a/qutebrowser/keyinput/modeparsers.py +++ b/qutebrowser/keyinput/modeparsers.py @@ -222,12 +222,9 @@ class HintKeyParser(keyparser.CommandKeyParser): class CaretKeyParser(keyparser.CommandKeyParser): - """KeyParser for Caret mode.""" + """KeyParser for caret mode.""" def __init__(self, win_id, parent=None): super().__init__(win_id, parent, supports_count=True, supports_chains=True) self.read_config('caret') - - def __repr__(self): - return utils.get_repr(self) From d992caf8fc7af00db9cde36211d08323a6fcf0a7 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 13 May 2015 22:44:37 +0200 Subject: [PATCH 36/36] Clean up statusbar caret handling. --- qutebrowser/mainwindow/statusbar/bar.py | 46 ++++++++++++------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/qutebrowser/mainwindow/statusbar/bar.py b/qutebrowser/mainwindow/statusbar/bar.py index 6df62e258..fe1a274cc 100644 --- a/qutebrowser/mainwindow/statusbar/bar.py +++ b/qutebrowser/mainwindow/statusbar/bar.py @@ -36,6 +36,7 @@ from qutebrowser.mainwindow.statusbar import text as textwidget PreviousWidget = usertypes.enum('PreviousWidget', ['none', 'prompt', 'command']) Severity = usertypes.enum('Severity', ['normal', 'warning', 'error']) +CaretMode = usertypes.enum('CaretMode', ['off', 'on', 'selection']) class StatusBar(QWidget): @@ -77,6 +78,11 @@ class StatusBar(QWidget): For some reason we need to have this as class attribute so pyqtProperty works correctly. + _caret_mode: The current caret mode (off/on/selection). + + For some reason we need to have this as class attribute + so pyqtProperty works correctly. + Signals: resized: Emitted when the statusbar has resized, so the completion widget can adjust its size to it. @@ -91,8 +97,7 @@ class StatusBar(QWidget): _severity = None _prompt_active = False _insert_active = False - _caret_active = False - _caret_selection_active = False + _caret_mode = CaretMode.off STYLESHEET = """ QWidget#StatusBar { @@ -103,11 +108,11 @@ class StatusBar(QWidget): {{ color['statusbar.bg.insert'] }} } - QWidget#StatusBar[caret_active="true"] { + QWidget#StatusBar[caret_mode="on"] { {{ color['statusbar.bg.caret'] }} } - QWidget#StatusBar[caret_selection_active="true"] { + QWidget#StatusBar[caret_mode="selection"] { {{ color['statusbar.bg.caret-selection'] }} } @@ -263,15 +268,10 @@ class StatusBar(QWidget): """Getter for self.insert_active, so it can be used as Qt property.""" return self._insert_active - @pyqtProperty(bool) - def caret_active(self): - """Getter for self.caret_active, so it can be used as Qt property.""" - return self._caret_active - - @pyqtProperty(bool) - def caret_selection_active(self): - """Getter for caret_selection_active, so it can be used as property.""" - return self._caret_selection_active + @pyqtProperty(str) + def caret_mode(self): + """Getter for self._caret_mode, so it can be used as Qt property.""" + return self._caret_mode.name def set_mode_active(self, mode, val): """Setter for self.{insert,caret}_active. @@ -283,19 +283,19 @@ class StatusBar(QWidget): log.statusbar.debug("Setting insert_active to {}".format(val)) self._insert_active = val elif mode == usertypes.KeyMode.caret: - log.statusbar.debug("Setting caret_active to {}".format(val)) webview = objreg.get("tabbed-browser", scope="window", window=self._win_id).currentWidget() - if val and webview.selection_enabled: - self._set_mode_text("{} selection".format(mode.name)) - self._caret_selection_active = val - self._caret_active = False - else: - if val: + log.statusbar.debug("Setting caret_mode - val {}, selection " + "{}".format(val, webview.selection_enabled)) + if val: + if webview.selection_enabled: + self._set_mode_text("{} selection".format(mode.name)) + self._caret_mode = CaretMode.selection + else: self._set_mode_text(mode.name) - self._caret_active = val - self._caret_selection_active = False - + self._caret_mode = CaretMode.on + else: + self._caret_mode = CaretMode.off self.setStyleSheet(style.get_stylesheet(self.STYLESHEET)) def _set_mode_text(self, mode):