From c483db63ce15b3e46a767dbfb85587d454e95b6e 2010-08-24 16:07:56
From: epatters <epatters@enthought.com>
Date: 2010-08-24 16:07:56
Subject: [PATCH] Reimplemented IPythonWidget's edit magic handling to support line numbers. Also, removed the code path for launching the file with the system default Python application, as this is too dangerous.

---

diff --git a/IPython/frontend/qt/console/ipython_widget.py b/IPython/frontend/qt/console/ipython_widget.py
index 91bbd83..eec36d2 100644
--- a/IPython/frontend/qt/console/ipython_widget.py
+++ b/IPython/frontend/qt/console/ipython_widget.py
@@ -1,3 +1,11 @@
+""" A FrontendWidget that emulates the interface of the console IPython and
+    supports the additional functionality provided by the IPython kernel.
+
+    TODO: Add support for retrieving the system default editor. Requires code
+          paths for Windows (use the registry), Mac OS (use LaunchServices), and
+          Linux (use the xdg system).
+"""
+
 # Standard library imports
 from subprocess import Popen
 
@@ -153,7 +161,7 @@ class IPythonWidget(FrontendWidget):
         """ Reimplemented to handle %edit and paging payloads.
         """
         if item['source'] == self._payload_source_edit:
-            self.edit(item['filename'], item['line_number'])
+            self._edit(item['filename'], item['line_number'])
             return True
         elif item['source'] == self._payload_source_page:
             self._page(item['data'])
@@ -218,61 +226,34 @@ class IPythonWidget(FrontendWidget):
     # 'IPythonWidget' interface
     #---------------------------------------------------------------------------
 
-    def edit(self, filename, line=None):
-        """ Opens a Python script for editing.
-
-        Parameters:
-        -----------
-        filename : str
-            A path to a local system file.
-
-        line : int, optional
-            A line of interest in the file.
-        
-        Raises:
-        -------
-        OSError
-            If the editor command cannot be executed.
-        """
-        if self._editor == 'default':
-            url = QtCore.QUrl.fromLocalFile(filename)
-            if not QtGui.QDesktopServices.openUrl(url):
-                message = 'Failed to open %s with the default application'
-                raise OSError(message % repr(filename))
-        elif self._editor is None:
-            self.custom_edit_requested.emit(filename, line)
-        else:
-            Popen(self._editor + [filename])
-
     def reset_styling(self):
         """ Restores the default IPythonWidget styling.
         """
         self.set_styling(self.default_stylesheet, syntax_style='default')
         #self.set_styling(self.dark_stylesheet, syntax_style='monokai')
 
-    def set_editor(self, editor):
+    def set_editor(self, editor, line_editor=None):
         """ Sets the editor to use with the %edit magic.
 
         Parameters:
         -----------
-        editor : str or sequence of str
-            A command suitable for use with Popen. This command will be executed
-            with a single argument--a filename--when editing is requested.
+        editor : str
+            A command for invoking a system text editor. If the string contains
+            a {filename} format specifier, it will be used. Otherwise, the 
+            filename will be appended to the end the command.
 
-            This parameter also takes two special values:
-                'default' : Files will be edited with the system default 
-                            application for Python files.
+            This parameter also takes a special value:
                 'custom'  : Emit a 'custom_edit_requested(str, int)' signal 
                             instead of opening an editor.
+
+        line_editor : str, optional
+            The editor command to use when a specific line number is
+            requested. The string should contain two format specifiers: {line}
+            and {filename}. If this parameter is not specified, the line number
+            option to the %edit magic will be ignored.
         """
-        if editor == 'default':
-            self._editor = 'default'
-        elif editor == 'custom':
-            self._editor = None
-        elif isinstance(editor, basestring):
-            self._editor = [ editor ]
-        else:
-            self._editor = list(editor)
+        self._editor = editor
+        self._editor_line = line_editor
 
     def set_styling(self, stylesheet, syntax_style=None):
         """ Sets the IPythonWidget styling.
@@ -303,6 +284,43 @@ class IPythonWidget(FrontendWidget):
     # 'IPythonWidget' protected interface
     #---------------------------------------------------------------------------
 
+    def _edit(self, filename, line=None):
+        """ Opens a Python script for editing.
+
+        Parameters:
+        -----------
+        filename : str
+            A path to a local system file.
+
+        line : int, optional
+            A line of interest in the file.
+        """
+        if self._editor == 'custom':
+            self.custom_edit_requested.emit(filename, line)
+        elif self._editor == 'default':
+            self._append_plain_text('No default editor available.\n')
+        else:
+            try:
+                filename = '"%s"' % filename
+                if line and self._editor_line:
+                    command = self._editor_line.format(filename=filename,
+                                                       line=line)
+                else:
+                    try:
+                        command = self._editor.format()
+                    except KeyError:
+                        command = self._editor.format(filename=filename)
+                    else:
+                        command += ' ' + filename
+            except KeyError:
+                self._append_plain_text('Invalid editor command.\n')
+            else:
+                try:
+                    Popen(command, shell=True)
+                except OSError:
+                    msg = 'Opening editor with command "%s" failed.\n'
+                    self._append_plain_text(msg % command)
+
     def _make_in_prompt(self, number):
         """ Given a prompt number, returns an HTML In prompt.
         """