Merge branch 'visual'
This commit is contained in:
commit
8941b5dc96
47
.eslintrc
Normal file
47
.eslintrc
Normal file
@ -0,0 +1,47 @@
|
||||
# vim: ft=yaml
|
||||
|
||||
env:
|
||||
browser: true
|
||||
|
||||
rules:
|
||||
block-scoped-var: 2
|
||||
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, "inside"]
|
||||
brace-style: [2, "1tbs", {"allowSingleLine": true}]
|
||||
comma-style: [2, "last"]
|
||||
consistent-this: [2, "self"]
|
||||
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": "never", "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, 30]
|
||||
no-bitwise: 2
|
||||
no-reserved-keys: 2
|
||||
global-strict: 0
|
||||
quotes: 0
|
@ -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
|
||||
@ -29,4 +30,5 @@ exclude qutebrowser.rcc
|
||||
exclude .coveragerc
|
||||
exclude .flake8
|
||||
exclude .pylintrc
|
||||
exclude .eslintrc
|
||||
exclude doc/help
|
||||
|
@ -38,8 +38,10 @@ 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
|
||||
from qutebrowser.misc import editor
|
||||
|
||||
|
||||
@ -1092,8 +1094,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.
|
||||
|
||||
@ -1213,6 +1214,285 @@ class CommandDispatcher:
|
||||
for _ in range(count):
|
||||
view.search(view.search_text, flags)
|
||||
|
||||
@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 selection to the next line.
|
||||
|
||||
Args:
|
||||
count: How many lines to move.
|
||||
"""
|
||||
webview = self._current_widget()
|
||||
if not webview.selection_enabled:
|
||||
act = QWebPage.MoveToNextLine
|
||||
else:
|
||||
act = QWebPage.SelectNextLine
|
||||
for _ in range(count):
|
||||
webview.triggerPageAction(act)
|
||||
|
||||
@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 selection to the prev line.
|
||||
|
||||
Args:
|
||||
count: How many lines to move.
|
||||
"""
|
||||
webview = self._current_widget()
|
||||
if not webview.selection_enabled:
|
||||
act = QWebPage.MoveToPreviousLine
|
||||
else:
|
||||
act = QWebPage.SelectPreviousLine
|
||||
for _ in range(count):
|
||||
webview.triggerPageAction(act)
|
||||
|
||||
@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 selection to the next char.
|
||||
|
||||
Args:
|
||||
count: How many lines to move.
|
||||
"""
|
||||
webview = self._current_widget()
|
||||
if not webview.selection_enabled:
|
||||
act = QWebPage.MoveToNextChar
|
||||
else:
|
||||
act = QWebPage.SelectNextChar
|
||||
for _ in range(count):
|
||||
webview.triggerPageAction(act)
|
||||
|
||||
@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 selection to the previous char.
|
||||
|
||||
Args:
|
||||
count: How many chars to move.
|
||||
"""
|
||||
webview = self._current_widget()
|
||||
if not webview.selection_enabled:
|
||||
act = QWebPage.MoveToPreviousChar
|
||||
else:
|
||||
act = QWebPage.SelectPreviousChar
|
||||
for _ in range(count):
|
||||
webview.triggerPageAction(act)
|
||||
|
||||
@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 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
|
||||
else:
|
||||
act = QWebPage.SelectNextWord
|
||||
for _ in range(count):
|
||||
webview.triggerPageAction(act)
|
||||
|
||||
@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 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]
|
||||
else:
|
||||
act = [QWebPage.SelectNextWord, QWebPage.SelectNextChar]
|
||||
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_prev_word(self, count=1):
|
||||
"""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
|
||||
else:
|
||||
act = QWebPage.SelectPreviousWord
|
||||
for _ in range(count):
|
||||
webview.triggerPageAction(act)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', hide=True,
|
||||
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
|
||||
for _ in range(count):
|
||||
webview.triggerPageAction(act)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', hide=True,
|
||||
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
|
||||
for _ in range(count):
|
||||
webview.triggerPageAction(act)
|
||||
|
||||
@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 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,
|
||||
QWebPage.MoveToStartOfBlock]
|
||||
else:
|
||||
act = [QWebPage.SelectEndOfBlock, QWebPage.SelectNextLine,
|
||||
QWebPage.SelectStartOfBlock]
|
||||
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_start_of_prev_block(self, count=1):
|
||||
"""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,
|
||||
QWebPage.MoveToStartOfBlock]
|
||||
else:
|
||||
act = [QWebPage.SelectStartOfBlock, QWebPage.SelectPreviousLine,
|
||||
QWebPage.SelectStartOfBlock]
|
||||
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_next_block(self, count=1):
|
||||
"""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,
|
||||
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 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,
|
||||
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')
|
||||
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
|
||||
else:
|
||||
act = QWebPage.SelectStartOfDocument
|
||||
webview.triggerPageAction(act)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', hide=True,
|
||||
modes=[KeyMode.caret], scope='window')
|
||||
def move_to_end_of_document(self):
|
||||
"""Move the cursor or selection to the end of the document."""
|
||||
webview = self._current_widget()
|
||||
if not webview.selection_enabled:
|
||||
act = QWebPage.MoveToEndOfDocument
|
||||
else:
|
||||
act = QWebPage.SelectEndOfDocument
|
||||
webview.triggerPageAction(act)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', hide=True,
|
||||
modes=[KeyMode.caret], scope='window')
|
||||
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.
|
||||
keep: If given, stay in visual mode after yanking.
|
||||
"""
|
||||
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))
|
||||
if not keep:
|
||||
modeman.leave(self._win_id, KeyMode.caret, "yank selected")
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', hide=True,
|
||||
modes=[KeyMode.caret], scope='window')
|
||||
def toggle_selection(self):
|
||||
"""Toggle caret selection mode."""
|
||||
widget = self._current_widget()
|
||||
widget.selection_enabled = not widget.selection_enabled
|
||||
mainwindow = objreg.get('main-window', scope='window',
|
||||
window=self._win_id)
|
||||
mainwindow.status.set_mode_active(usertypes.KeyMode.caret, True)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', hide=True,
|
||||
modes=[KeyMode.caret], scope='window')
|
||||
def drop_selection(self):
|
||||
"""Drop selection and keep selection mode enabled."""
|
||||
self._current_widget().triggerPageAction(QWebPage.MoveToNextChar)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window',
|
||||
count='count', debug=True)
|
||||
def debug_webaction(self, action, count=1):
|
||||
|
@ -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)
|
||||
@ -443,6 +444,18 @@ class WebView(QWebView):
|
||||
log.webview.debug("Ignoring focus because mode {} was "
|
||||
"entered.".format(mode))
|
||||
self.setFocusPolicy(Qt.NoFocus)
|
||||
elif mode == usertypes.KeyMode.caret:
|
||||
settings = self.settings()
|
||||
settings.setAttribute(QWebSettings.CaretBrowsingEnabled, True)
|
||||
self.selection_enabled = False
|
||||
|
||||
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'))
|
||||
|
||||
@pyqtSlot(usertypes.KeyMode)
|
||||
def on_mode_left(self, mode):
|
||||
@ -451,6 +464,15 @@ class WebView(QWebView):
|
||||
usertypes.KeyMode.yesno):
|
||||
log.webview.debug("Restoring focus policy because mode {} was "
|
||||
"left.".format(mode))
|
||||
elif mode == usertypes.KeyMode.caret:
|
||||
settings = self.settings()
|
||||
if settings.testAttribute(QWebSettings.CaretBrowsingEnabled):
|
||||
if self.selection_enabled and self.hasSelection():
|
||||
# Remove selection if it exists
|
||||
self.triggerPageAction(QWebPage.MoveToNextChar)
|
||||
settings.setAttribute(QWebSettings.CaretBrowsingEnabled, False)
|
||||
self.selection_enabled = False
|
||||
|
||||
self.setFocusPolicy(Qt.WheelFocus)
|
||||
|
||||
def search(self, text, flags):
|
||||
|
@ -832,6 +832,15 @@ def data(readonly=False):
|
||||
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.caret-selection',
|
||||
SettingValue(typ.QssColor(), '#a12dff'),
|
||||
"Background color of the statusbar in caret mode with a "
|
||||
"selection"),
|
||||
|
||||
('statusbar.progress.bg',
|
||||
SettingValue(typ.QssColor(), 'white'),
|
||||
"Background color of the progress bar."),
|
||||
@ -1104,6 +1113,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': (
|
||||
""),
|
||||
}
|
||||
|
||||
|
||||
@ -1169,6 +1180,7 @@ KEY_DATA = collections.OrderedDict([
|
||||
('search-next', ['n']),
|
||||
('search-prev', ['N']),
|
||||
('enter-mode insert', ['i']),
|
||||
('enter-mode caret', ['v']),
|
||||
('yank', ['yy']),
|
||||
('yank -s', ['yY']),
|
||||
('yank -t', ['yt']),
|
||||
@ -1269,6 +1281,33 @@ KEY_DATA = collections.OrderedDict([
|
||||
('rl-delete-char', ['<Ctrl-?>']),
|
||||
('rl-backward-delete-char', ['<Ctrl-H>']),
|
||||
])),
|
||||
|
||||
('caret', collections.OrderedDict([
|
||||
('toggle-selection', ['v', '<Space>']),
|
||||
('drop-selection', ['<Ctrl-Space>']),
|
||||
('enter-mode normal', ['c']),
|
||||
('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-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']),
|
||||
('move-to-end-of-document', ['G']),
|
||||
('yank-selected -p', ['Y']),
|
||||
('yank-selected', ['y', '<Return>', '<Ctrl-J>']),
|
||||
('scroll -50 0', ['H']),
|
||||
('scroll 0 50', ['J']),
|
||||
('scroll 0 -50', ['K']),
|
||||
('scroll 50 0', ['L']),
|
||||
])),
|
||||
])
|
||||
|
||||
|
||||
|
110
qutebrowser/javascript/position_caret.js
Normal file
110
qutebrowser/javascript/position_caret.js
Normal file
@ -0,0 +1,110 @@
|
||||
/**
|
||||
* Copyright 2015 Artur Shaik <ashaihullin@gmail.com>
|
||||
* Copyright 2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/* eslint-disable max-len */
|
||||
|
||||
/**
|
||||
* 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());
|
||||
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 === undefined) {
|
||||
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;
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
|
||||
(function() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
})();
|
@ -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
|
||||
|
@ -78,6 +78,7 @@ 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),
|
||||
}
|
||||
objreg.register('keyparsers', keyparsers, scope='window', window=win_id)
|
||||
modeman.destroyed.connect(
|
||||
@ -92,6 +93,7 @@ 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)
|
||||
return modeman
|
||||
|
||||
|
||||
|
@ -218,3 +218,13 @@ 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')
|
||||
|
@ -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,6 +97,7 @@ class StatusBar(QWidget):
|
||||
_severity = None
|
||||
_prompt_active = False
|
||||
_insert_active = False
|
||||
_caret_mode = CaretMode.off
|
||||
|
||||
STYLESHEET = """
|
||||
QWidget#StatusBar {
|
||||
@ -101,6 +108,14 @@ class StatusBar(QWidget):
|
||||
{{ color['statusbar.bg.insert'] }}
|
||||
}
|
||||
|
||||
QWidget#StatusBar[caret_mode="on"] {
|
||||
{{ color['statusbar.bg.caret'] }}
|
||||
}
|
||||
|
||||
QWidget#StatusBar[caret_mode="selection"] {
|
||||
{{ color['statusbar.bg.caret-selection'] }}
|
||||
}
|
||||
|
||||
QWidget#StatusBar[prompt_active="true"] {
|
||||
{{ color['statusbar.bg.prompt'] }}
|
||||
}
|
||||
@ -253,14 +268,34 @@ 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(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.
|
||||
|
||||
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:
|
||||
webview = objreg.get("tabbed-browser", scope="window",
|
||||
window=self._win_id).currentWidget()
|
||||
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_mode = CaretMode.on
|
||||
else:
|
||||
self._caret_mode = CaretMode.off
|
||||
self.setStyleSheet(style.get_stylesheet(self.STYLESHEET))
|
||||
|
||||
def _set_mode_text(self, mode):
|
||||
@ -438,8 +473,8 @@ 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):
|
||||
self.set_mode_active(mode, True)
|
||||
|
||||
@pyqtSlot(usertypes.KeyMode, usertypes.KeyMode)
|
||||
def on_mode_left(self, old_mode, new_mode):
|
||||
@ -451,8 +486,8 @@ 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):
|
||||
self.set_mode_active(old_mode, False)
|
||||
|
||||
@config.change_filter('ui', 'message-timeout')
|
||||
def set_pop_timer_interval(self):
|
||||
|
@ -512,7 +512,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):
|
||||
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,
|
||||
|
@ -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'])
|
||||
|
||||
|
||||
# Available command completions
|
||||
|
@ -39,7 +39,8 @@ BINDINGS = {'test': {'<Ctrl-a>': 'ctrla',
|
||||
'a': 'a',
|
||||
'ba': 'ba',
|
||||
'ax': 'ax',
|
||||
'ccc': 'ccc'},
|
||||
'ccc': 'ccc',
|
||||
'0': '0'},
|
||||
'test2': {'foo': 'bar', '<Ctrl+X>': 'ctrlx'}}
|
||||
|
||||
|
||||
@ -198,6 +199,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, config_stub,
|
||||
mocker):
|
||||
"""Test ambiguous keychain."""
|
||||
@ -251,7 +258,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):
|
||||
|
Loading…
Reference in New Issue
Block a user