Merge branch 'editor'

Conflicts:
	qutebrowser/browser/commands.py
	qutebrowser/utils/misc.py
This commit is contained in:
Florian Bruhin 2014-05-21 15:37:18 +02:00
commit 60f25373e3
2 changed files with 112 additions and 49 deletions

View File

@ -34,7 +34,7 @@ import qutebrowser.browser.hints as hints
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.utils.webelem as webelem import qutebrowser.utils.webelem as webelem
from qutebrowser.utils.misc import check_overflow from qutebrowser.utils.misc import check_overflow, shell_escape, ExternalEditor
from qutebrowser.utils.misc import shell_escape from qutebrowser.utils.misc import shell_escape
from qutebrowser.commands.exceptions import CommandError from qutebrowser.commands.exceptions import CommandError
@ -51,6 +51,7 @@ class CommandDispatcher(QObject):
Attributes: Attributes:
_tabs: The TabbedBrowser object. _tabs: The TabbedBrowser object.
_editor: The ExternalEditor object.
""" """
def __init__(self, parent): def __init__(self, parent):
@ -61,6 +62,7 @@ class CommandDispatcher(QObject):
""" """
super().__init__(parent) super().__init__(parent)
self._tabs = parent self._tabs = parent
self._editor = None
def _scroll_percent(self, perc=None, count=None, orientation=None): def _scroll_percent(self, perc=None, count=None, orientation=None):
"""Inner logic for scroll_percent_(x|y). """Inner logic for scroll_percent_(x|y).
@ -601,56 +603,24 @@ class CommandDispatcher(QObject):
elem = frame.findFirstElement(webelem.SELECTORS[ elem = frame.findFirstElement(webelem.SELECTORS[
webelem.Group.editable_focused]) webelem.Group.editable_focused])
if elem.isNull(): if elem.isNull():
raise CommandError("No editable element focused!") message.error("No editable element focused!")
oshandle, filename = mkstemp(text=True) return
text = elem.evaluateJavaScript('this.value') text = elem.evaluateJavaScript('this.value')
if text: self._editor = ExternalEditor()
with open(filename, 'w', encoding='utf-8') as f: self._editor.editing_finished.connect(partial(self.on_editing_finished, elem))
f.write(text) self._editor.edit(text)
proc = QProcess(self)
proc.finished.connect(partial(self.on_editor_closed, elem, oshandle,
filename))
proc.error.connect(partial(self.on_editor_error, 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, def on_editing_finished(self, elem, text):
exitstatus):
"""Write the editor text into the form field and clean up tempfile. """Write the editor text into the form field and clean up tempfile.
Callback for QProcess when the editor was closed. Callback for QProcess when the editor was closed.
"""
logging.debug("Editor closed")
if exitcode != 0:
raise CommandError("Editor did quit abnormally (status "
"{})!".format(exitcode))
if exitstatus != QProcess.NormalExit:
# No error here, since we already handle this in on_editor_error
return
if elem.isNull():
raise CommandError("Element vanished while editing!")
with open(filename, 'r', encoding='utf-8') as f:
text = ''.join(f.readlines())
text = webelem.javascript_escape(text)
logging.debug("Read back: {}".format(text))
elem.evaluateJavaScript("this.value='{}'".format(text))
self._editor_cleanup(oshandle, filename)
def on_editor_error(self, oshandle, filename, error): Args:
"""Display an error message and clean up when editor crashed.""" elem: The QWebElement which was modified.
messages = { text: The new text to insert.
QProcess.FailedToStart: "The process failed to start.", """
QProcess.Crashed: "The process crashed.", if elem.isNull():
QProcess.Timedout: "The last waitFor...() function timed out.", message.error("Element vanished while editing!")
QProcess.WriteError: ("An error occurred when attempting to write " return
"to the process."), text = webelem.javascript_escape(text)
QProcess.ReadError: ("An error occurred when attempting to read " elem.evaluateJavaScript("this.value='{}'".format(text))
"from the process."),
QProcess.UnknownError: "An unknown error occurred.",
}
self._editor_cleanup(oshandle, filename)
raise CommandError("Error while calling editor: {}".format(
messages[error]))

View File

@ -27,13 +27,19 @@ import re
import sys import sys
import shlex import shlex
import os.path import os.path
import logging
import urllib.request import urllib.request
from tempfile import mkstemp
from urllib.parse import urljoin, urlencode from urllib.parse import urljoin, urlencode
from functools import reduce from functools import reduce
from PyQt5.QtCore import QCoreApplication, QStandardPaths from PyQt5.QtCore import (pyqtSignal, QCoreApplication, QStandardPaths,
QProcess, QObject)
from pkg_resources import resource_string
import qutebrowser import qutebrowser
import qutebrowser.config.config as config
import qutebrowser.utils.message as message
MAXVALS = { MAXVALS = {
@ -239,3 +245,90 @@ def actute_warning():
"https://bugs.freedesktop.org/show_bug.cgi?id=69476 for " "https://bugs.freedesktop.org/show_bug.cgi?id=69476 for "
"details.") "details.")
break break
class ExternalEditor(QObject):
"""Class to simplify editing a text in an external editor."""
editing_finished = pyqtSignal(str)
def __init__(self, parent=None):
super().__init__(parent)
self.text = None
self.oshandle = None
self.filename = None
self.proc = None
def _cleanup(self):
os.close(self.oshandle)
try:
os.remove(self.filename)
except PermissionError:
message.error("Failed to delete tempfile...")
self.text = None
self.oshandle = None
self.filename = None
self.proc = None
def on_proc_closed(self, exitcode, exitstatus):
"""Write the editor text into the form field and clean up tempfile.
Callback for QProcess when the editor was closed.
Emit:
editing_finished: If process exited normally.
"""
logging.debug("Editor closed")
if exitcode != 0:
message.error("Editor did quit abnormally (status {})!".format(
exitcode))
return
if exitstatus != QProcess.NormalExit:
# No error here, since we already handle this in on_editor_error
return
with open(self.filename, 'r') as f:
text = ''.join(f.readlines())
logging.debug("Read back: {}".format(text))
self._cleanup()
self.editing_finished.emit(text)
def on_proc_error(self, error):
"""Display an error message and clean up when editor crashed."""
messages = {
QProcess.FailedToStart: "The process failed to start.",
QProcess.Crashed: "The process crashed.",
QProcess.Timedout: "The last waitFor...() function timed out.",
QProcess.WriteError: ("An error occurred when attempting to write "
"to the process."),
QProcess.ReadError: ("An error occurred when attempting to read "
"from the process."),
QProcess.UnknownError: "An unknown error occurred.",
}
message.error("Error while calling editor: {}".format(messages[error]))
self._cleanup()
def edit(self, text):
"""Edit a given text.
Args:
text: The initial text to edit.
Emit:
editing_finished with the new text if editing finshed successfully.
"""
if self.text is not None:
raise ValueError("Already editing a file!")
self.text = text
self.oshandle, self.filename = mkstemp(text=True)
if not text:
with open(self.filename, 'w') as f:
f.write(text)
self.proc = QProcess(self)
self.proc.finished.connect(self.on_proc_closed)
self.proc.error.connect(self.on_proc_error)
editor = config.get('general', 'editor')
executable = editor[0]
args = [arg.replace('{}', self.filename) for arg in editor[1:]]
logging.debug("Calling \"{}\" with args {}".format(executable, args))
self.proc.start(executable, args)