From aeaa20c3b7fbd00d17f2da01a03dbec3e8ad9464 Mon Sep 17 00:00:00 2001 From: Artur Shaik Date: Mon, 4 May 2015 18:00:40 +0600 Subject: [PATCH 1/7] 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 2/7] 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 3/7] 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 4/7] 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 5/7] 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 6/7] 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 7/7] 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); +}