Merge pull request #653 from artur-shaik/visual

Visual
This commit is contained in:
Florian Bruhin 2015-05-11 20:19:01 +02:00
commit 012e124eaf
10 changed files with 258 additions and 123 deletions

View File

@ -1153,152 +1153,190 @@ class CommandDispatcher:
view.search(view.search_text, flags)
@cmdutils.register(instance='command-dispatcher', hide=True,
modes=[KeyMode.caret, KeyMode.visual], 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."""
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)
for _ in range(count):
webview.triggerPageAction(act)
@cmdutils.register(instance='command-dispatcher', hide=True,
modes=[KeyMode.caret, KeyMode.visual], 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."""
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)
for _ in range(count):
webview.triggerPageAction(act)
@cmdutils.register(instance='command-dispatcher', hide=True,
modes=[KeyMode.caret, KeyMode.visual], 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."""
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)
for _ in range(count):
webview.triggerPageAction(act)
@cmdutils.register(instance='command-dispatcher', hide=True,
modes=[KeyMode.caret, KeyMode.visual], 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."""
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)
for _ in range(count):
webview.triggerPageAction(act)
@cmdutils.register(instance='command-dispatcher', hide=True,
modes=[KeyMode.caret, KeyMode.visual], 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."""
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)
for _ in range(count):
webview.triggerPageAction(act)
@cmdutils.register(instance='command-dispatcher', hide=True,
modes=[KeyMode.caret, KeyMode.visual],
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."""
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)
for _ in range(count):
for a in act:
webview.triggerPageAction(a)
@cmdutils.register(instance='command-dispatcher', hide=True,
modes=[KeyMode.caret, KeyMode.visual], 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."""
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)
for _ in range(count):
webview.triggerPageAction(act)
@cmdutils.register(instance='command-dispatcher', hide=True,
modes=[KeyMode.caret, KeyMode.visual], 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."""
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)
for _ in range(count):
webview.triggerPageAction(act)
@cmdutils.register(instance='command-dispatcher', hide=True,
modes=[KeyMode.caret, KeyMode.visual], 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."""
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)
for _ in range(count):
webview.triggerPageAction(act)
@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 == KeyMode.caret:
act = QWebPage.MoveToStartOfBlock
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."""
webview = self._current_widget()
if not webview.selection_enabled:
act = [QWebPage.MoveToEndOfBlock, QWebPage.MoveToNextLine, QWebPage.MoveToStartOfBlock]
else:
act = QWebPage.SelectStartOfBlock
self._current_widget().triggerPageAction(act)
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, 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 == KeyMode.caret:
act = QWebPage.MoveToEndOfBlock
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."""
webview = self._current_widget()
if not webview.selection_enabled:
act = [QWebPage.MoveToStartOfBlock, QWebPage.MoveToPreviousLine, QWebPage.MoveToStartOfBlock]
else:
act = QWebPage.SelectEndOfBlock
self._current_widget().triggerPageAction(act)
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, KeyMode.visual], scope='window')
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')
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 +1361,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',

View File

@ -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)
@ -435,11 +436,18 @@ 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.page().currentFrame().evaluateJavaScript(
utils.read_file('javascript/position_caret.js'))
@pyqtSlot(usertypes.KeyMode)
def on_mode_left(self, mode):
@ -448,13 +456,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)

View File

@ -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', ['<Ctrl-H>']),
])),
('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', ['<Space>']),
('drop-selection', ['<Ctrl-Space>']),
('enter-mode normal', ['c']),
])),
('caret,visual', collections.OrderedDict([
('move-to-next-line', ['j']),
('move-to-prev-line', ['k']),
('move-to-next-char', ['l']),
@ -1279,10 +1270,16 @@ 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']),
('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']),

View File

@ -0,0 +1,89 @@
/**
* Copyright 2014-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/>.
*/
/**
* 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);
}

View File

@ -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

View File

@ -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

View File

@ -232,15 +232,3 @@ class CaretKeyParser(keyparser.CommandKeyParser):
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)

View File

@ -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_selection_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')

View File

@ -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,

View File

@ -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