Add open_editor command
This commit is contained in:
parent
6420a7abfc
commit
bc02df0bde
@ -17,14 +17,21 @@
|
|||||||
|
|
||||||
"""The main tabbed browser widget."""
|
"""The main tabbed browser widget."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
from tempfile import mkstemp
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
from PyQt5.QtWidgets import QApplication
|
from PyQt5.QtWidgets import QApplication
|
||||||
from PyQt5.QtCore import pyqtSlot, Qt, QObject
|
from PyQt5.QtCore import pyqtSlot, Qt, QObject, QProcess
|
||||||
from PyQt5.QtGui import QClipboard
|
from PyQt5.QtGui import QClipboard
|
||||||
from PyQt5.QtPrintSupport import QPrinter, QPrintDialog, QPrintPreviewDialog
|
from PyQt5.QtPrintSupport import QPrinter, QPrintDialog, QPrintPreviewDialog
|
||||||
|
|
||||||
import qutebrowser.utils.url as urlutils
|
import qutebrowser.utils.url as urlutils
|
||||||
import qutebrowser.utils.message as message
|
import qutebrowser.utils.message as message
|
||||||
import qutebrowser.commands.utils as cmdutils
|
import qutebrowser.commands.utils as cmdutils
|
||||||
|
import qutebrowser.utils.webelem as webelem
|
||||||
|
import qutebrowser.config.config as config
|
||||||
|
|
||||||
|
|
||||||
class CurCommandDispatcher(QObject):
|
class CurCommandDispatcher(QObject):
|
||||||
@ -331,3 +338,51 @@ class CurCommandDispatcher(QObject):
|
|||||||
"""
|
"""
|
||||||
tab = self._tabs.currentWidget()
|
tab = self._tabs.currentWidget()
|
||||||
tab.zoom(-count)
|
tab.zoom(-count)
|
||||||
|
|
||||||
|
@cmdutils.register(instance='mainwindow.tabs.cur', modes=['insert'],
|
||||||
|
name='open_editor', hide=True)
|
||||||
|
def editor(self):
|
||||||
|
"""Open an external editor with the current form field."""
|
||||||
|
frame = self._tabs.currentWidget().page_.currentFrame()
|
||||||
|
elem = frame.findFirstElement(webelem.SELECTORS['editable_focused'])
|
||||||
|
if elem.isNull():
|
||||||
|
message.error("No editable element focused!")
|
||||||
|
return
|
||||||
|
oshandle, filename = mkstemp(text=True)
|
||||||
|
text = elem.evaluateJavaScript('this.value')
|
||||||
|
if text:
|
||||||
|
with open(filename, 'w') as f:
|
||||||
|
f.write(text)
|
||||||
|
proc = QProcess(self)
|
||||||
|
proc.finished.connect(partial(self.on_editor_closed, elem, oshandle,
|
||||||
|
filename))
|
||||||
|
editor = config.get('general', 'editor')
|
||||||
|
executable = editor[0]
|
||||||
|
args = [arg.replace('{}', filename) for arg in editor[1:]]
|
||||||
|
logging.debug("Calling \"{}\" with args {}".format(executable, args))
|
||||||
|
proc.start(executable, args)
|
||||||
|
|
||||||
|
def on_editor_closed(self, elem, oshandle, filename, exitcode,
|
||||||
|
exitstatus):
|
||||||
|
"""Gets called by QProcess when the editor was closed.
|
||||||
|
|
||||||
|
Writes the editor text into the form field.
|
||||||
|
"""
|
||||||
|
logging.debug("Editor closed")
|
||||||
|
if exitcode != 0 or exitstatus != QProcess.NormalExit:
|
||||||
|
message.error("Editor did quit abnormally (status {})!".format(
|
||||||
|
exitcode))
|
||||||
|
return
|
||||||
|
if elem.isNull():
|
||||||
|
message.error("Element vanished while editing!")
|
||||||
|
return
|
||||||
|
with open(filename, 'r') as f:
|
||||||
|
text = ''.join(f.readlines())
|
||||||
|
text = webelem.javascript_escape(text)
|
||||||
|
logging.debug("Read back: {}".format(text))
|
||||||
|
elem.evaluateJavaScript("this.value='{}'".format(text))
|
||||||
|
os.close(oshandle)
|
||||||
|
try:
|
||||||
|
os.remove(filename)
|
||||||
|
except PermissionError:
|
||||||
|
message.error("Failed to delete tempfile...")
|
||||||
|
@ -17,6 +17,8 @@
|
|||||||
|
|
||||||
"""Setting options used for qutebrowser."""
|
"""Setting options used for qutebrowser."""
|
||||||
|
|
||||||
|
import shlex
|
||||||
|
|
||||||
from PyQt5.QtGui import QColor
|
from PyQt5.QtGui import QColor
|
||||||
|
|
||||||
import qutebrowser.commands.utils as cmdutils
|
import qutebrowser.commands.utils as cmdutils
|
||||||
@ -168,6 +170,29 @@ class String(BaseType):
|
|||||||
self.maxlen))
|
self.maxlen))
|
||||||
|
|
||||||
|
|
||||||
|
class ShellCommand(String):
|
||||||
|
|
||||||
|
"""A shellcommand which is split via shlex.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
placeholder: If there should be a placeholder.
|
||||||
|
"""
|
||||||
|
|
||||||
|
typestr = 'shell-cmd'
|
||||||
|
|
||||||
|
def __init__(self, placeholder=False):
|
||||||
|
self.placeholder = placeholder
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def validate(self, value):
|
||||||
|
super().validate(value)
|
||||||
|
if self.placeholder and '{}' not in value:
|
||||||
|
raise ValidationError(value, "needs to contain a {}-placeholder.")
|
||||||
|
|
||||||
|
def transform(self, value):
|
||||||
|
return shlex.split(value)
|
||||||
|
|
||||||
|
|
||||||
class Bool(BaseType):
|
class Bool(BaseType):
|
||||||
|
|
||||||
"""Base class for a boolean setting.
|
"""Base class for a boolean setting.
|
||||||
|
@ -84,7 +84,9 @@ SECTION_DESC = {
|
|||||||
"Keybindings for insert mode.\n"
|
"Keybindings for insert mode.\n"
|
||||||
"Since normal keypresses are passed through, only special keys are "
|
"Since normal keypresses are passed through, only special keys are "
|
||||||
"supported in this mode.\n"
|
"supported in this mode.\n"
|
||||||
"An useful command to map here is the hidden command leave_mode."),
|
"Useful hidden commands to map in this section:\n"
|
||||||
|
" open_editor: Open a texteditor with the focused field.\n"
|
||||||
|
" leave_mode: Leave the command mode."),
|
||||||
'keybind.hint': (
|
'keybind.hint': (
|
||||||
"Keybindings for hint mode.\n"
|
"Keybindings for hint mode.\n"
|
||||||
"Since normal keypresses are passed through, only special keys are "
|
"Since normal keypresses are passed through, only special keys are "
|
||||||
@ -177,6 +179,11 @@ DATA = OrderedDict([
|
|||||||
('background-tabs',
|
('background-tabs',
|
||||||
SettingValue(types.Bool(), 'false'),
|
SettingValue(types.Bool(), 'false'),
|
||||||
"Whether to open new tabs (middleclick/ctrl+click) in background"),
|
"Whether to open new tabs (middleclick/ctrl+click) in background"),
|
||||||
|
|
||||||
|
('editor',
|
||||||
|
SettingValue(types.ShellCommand(placeholder=True), 'gvim -f "{}"'),
|
||||||
|
"The editor (and arguments) to use for the open_editor binding. "
|
||||||
|
"Use {} for the filename. Gets split via shutils."),
|
||||||
)),
|
)),
|
||||||
|
|
||||||
('completion', sect.KeyValue(
|
('completion', sect.KeyValue(
|
||||||
@ -468,6 +475,7 @@ DATA = OrderedDict([
|
|||||||
('keybind.insert', sect.ValueList(
|
('keybind.insert', sect.ValueList(
|
||||||
types.KeyBindingName(), types.KeyBinding(),
|
types.KeyBindingName(), types.KeyBinding(),
|
||||||
('<Escape>', 'leave_mode'),
|
('<Escape>', 'leave_mode'),
|
||||||
|
('<Ctrl-E>', 'open_editor'),
|
||||||
)),
|
)),
|
||||||
|
|
||||||
('keybind.hint', sect.ValueList(
|
('keybind.hint', sect.ValueList(
|
||||||
|
@ -72,3 +72,24 @@ def is_visible(e, frame=None):
|
|||||||
# out of screen
|
# out of screen
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def javascript_escape(text):
|
||||||
|
"""Escapes special values in strings.
|
||||||
|
|
||||||
|
This maybe makes them work with QWebElement::evaluateJavaScript.
|
||||||
|
Maybe.
|
||||||
|
"""
|
||||||
|
# This is a list of tuples because order matters, and using OrderedDict
|
||||||
|
# makes no sense because we don't actually need dict-like properties.
|
||||||
|
replacements = [
|
||||||
|
('\\', r'\\'),
|
||||||
|
('\n', r'\n'),
|
||||||
|
('\t', r'\t'),
|
||||||
|
("'", r"\'"),
|
||||||
|
('"', r'\"'),
|
||||||
|
]
|
||||||
|
text = text.rstrip('\n')
|
||||||
|
for orig, repl in replacements:
|
||||||
|
text = text.replace(orig, repl)
|
||||||
|
return text
|
||||||
|
Loading…
Reference in New Issue
Block a user