diff --git a/python/helpers/pycharm/pycharm_load_entry_point.py b/python/helpers/pycharm/pycharm_load_entry_point.py index ecf4f905080f..5eadeb1a3516 100644 --- a/python/helpers/pycharm/pycharm_load_entry_point.py +++ b/python/helpers/pycharm/pycharm_load_entry_point.py @@ -4,6 +4,9 @@ from pkg_resources import load_entry_point if __name__ == '__main__': dist = os.environ.get("PYCHARM_EP_DIST") name = os.environ.get("PYCHARM_EP_NAME") - sys.exit( - load_entry_point(dist, "console_scripts", name)() - ) + if dist == "ipython" and name == "ipython": + from IPython import start_ipython + f = start_ipython + else: + f = load_entry_point(dist, "console_scripts", name) + sys.exit(f()) diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_comm.py b/python/helpers/pydev/_pydevd_bundle/pydevd_comm.py index 59e78f140254..60ba0ac8d049 100644 --- a/python/helpers/pydev/_pydevd_bundle/pydevd_comm.py +++ b/python/helpers/pydev/_pydevd_bundle/pydevd_comm.py @@ -58,10 +58,9 @@ each command has a format: * PYDB - pydevd, the python end ''' -from _pydev_imps import _pydev_threading as threading +from _pydev_bundle.pydev_imports import _queue from _pydev_imps import _pydev_time as time, _pydev_thread from _pydev_imps._pydev_socket import socket, AF_INET, SOCK_STREAM, SHUT_RD, SHUT_WR -from _pydev_bundle.pydev_imports import _queue from _pydevd_bundle.pydevd_constants import * #@UnusedWildImport try: @@ -115,10 +114,9 @@ CMD_REMOVE_DJANGO_EXCEPTION_BREAK = 126 CMD_SET_NEXT_STATEMENT = 127 CMD_SMART_STEP_INTO = 128 CMD_EXIT = 129 + CMD_SIGNATURE_CALL_TRACE = 130 - - CMD_SET_PY_EXCEPTION = 131 CMD_GET_FILE_CONTENTS = 132 CMD_SET_PROPERTY_TRACE = 133 @@ -142,35 +140,36 @@ CMD_RETURN = 502 CMD_ERROR = 901 ID_TO_MEANING = { - '101':'CMD_RUN', - '102':'CMD_LIST_THREADS', - '103':'CMD_THREAD_CREATE', - '104':'CMD_THREAD_KILL', - '105':'CMD_THREAD_SUSPEND', - '106':'CMD_THREAD_RUN', - '107':'CMD_STEP_INTO', - '108':'CMD_STEP_OVER', - '109':'CMD_STEP_RETURN', - '110':'CMD_GET_VARIABLE', - '111':'CMD_SET_BREAK', - '112':'CMD_REMOVE_BREAK', - '113':'CMD_EVALUATE_EXPRESSION', - '114':'CMD_GET_FRAME', - '115':'CMD_EXEC_EXPRESSION', - '116':'CMD_WRITE_TO_CONSOLE', - '117':'CMD_CHANGE_VARIABLE', - '118':'CMD_RUN_TO_LINE', - '119':'CMD_RELOAD_CODE', - '120':'CMD_GET_COMPLETIONS', - '121':'CMD_CONSOLE_EXEC', - '122':'CMD_ADD_EXCEPTION_BREAK', - '123':'CMD_REMOVE_EXCEPTION_BREAK', - '124':'CMD_LOAD_SOURCE', - '125':'CMD_ADD_DJANGO_EXCEPTION_BREAK', - '126':'CMD_REMOVE_DJANGO_EXCEPTION_BREAK', - '127':'CMD_SET_NEXT_STATEMENT', - '128':'CMD_SMART_STEP_INTO', + '101': 'CMD_RUN', + '102': 'CMD_LIST_THREADS', + '103': 'CMD_THREAD_CREATE', + '104': 'CMD_THREAD_KILL', + '105': 'CMD_THREAD_SUSPEND', + '106': 'CMD_THREAD_RUN', + '107': 'CMD_STEP_INTO', + '108': 'CMD_STEP_OVER', + '109': 'CMD_STEP_RETURN', + '110': 'CMD_GET_VARIABLE', + '111': 'CMD_SET_BREAK', + '112': 'CMD_REMOVE_BREAK', + '113': 'CMD_EVALUATE_EXPRESSION', + '114': 'CMD_GET_FRAME', + '115': 'CMD_EXEC_EXPRESSION', + '116': 'CMD_WRITE_TO_CONSOLE', + '117': 'CMD_CHANGE_VARIABLE', + '118': 'CMD_RUN_TO_LINE', + '119': 'CMD_RELOAD_CODE', + '120': 'CMD_GET_COMPLETIONS', + '121': 'CMD_CONSOLE_EXEC', + '122': 'CMD_ADD_EXCEPTION_BREAK', + '123': 'CMD_REMOVE_EXCEPTION_BREAK', + '124': 'CMD_LOAD_SOURCE', + '125': 'CMD_ADD_DJANGO_EXCEPTION_BREAK', + '126': 'CMD_REMOVE_DJANGO_EXCEPTION_BREAK', + '127': 'CMD_SET_NEXT_STATEMENT', + '128': 'CMD_SMART_STEP_INTO', '129': 'CMD_EXIT', + '130': 'CMD_SIGNATURE_CALL_TRACE', '131': 'CMD_SET_PY_EXCEPTION', @@ -184,14 +183,13 @@ ID_TO_MEANING = { '139': 'CMD_SEND_CURR_EXCEPTION_TRACE_PROCEEDED', '140': 'CMD_IGNORE_THROWN_EXCEPTION_AT', '141': 'CMD_ENABLE_DONT_TRACE', + '143': 'CMD_GET_ARRAY', + '144': 'CMD_STEP_INTO_MY_CODE', + '145': 'CMD_GET_CONCURRENCY_EVENT', - '501':'CMD_VERSION', - '502':'CMD_RETURN', - '901':'CMD_ERROR', - - '143':'CMD_GET_ARRAY', - '144':'CMD_STEP_INTO_MY_CODE', - '145':'CMD_GET_CONCURRENCY_EVENT', + '501': 'CMD_VERSION', + '502': 'CMD_RETURN', + '901': 'CMD_ERROR', } MAX_IO_MSG_SIZE = 1000 #if the io is too big, we'll not send all (could make the debugger too non-responsive) diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_frame.py b/python/helpers/pydev/_pydevd_bundle/pydevd_frame.py index b449dee9c89d..f03c41b44173 100644 --- a/python/helpers/pydev/_pydevd_bundle/pydevd_frame.py +++ b/python/helpers/pydev/_pydevd_bundle/pydevd_frame.py @@ -13,13 +13,14 @@ from _pydevd_bundle.pydevd_comm import CMD_STEP_CAUGHT_EXCEPTION, CMD_STEP_RETUR from _pydevd_bundle.pydevd_constants import STATE_SUSPEND, dict_contains, get_thread_id, STATE_RUN, dict_iter_values from _pydevd_bundle.pydevd_frame_utils import add_exception_to_frame, just_raised from pydevd_file_utils import get_abs_path_real_path_and_base_from_frame + try: from inspect import CO_GENERATOR except: CO_GENERATOR = 0 try: - from _pydevd_bundle.pydevd_signature import send_signature_call_trace + from _pydevd_bundle.pydevd_signature import send_signature_call_trace, send_signature_return_trace except ImportError: def send_signature_call_trace(*args, **kwargs): pass @@ -80,6 +81,12 @@ class PyDBFrame: # No longer cdef because object was dying when only a reference return self.trace_exception + def trace_return(self, frame, event, arg): + if event == 'return': + main_debugger, filename, info, thread = self._args + send_signature_return_trace(main_debugger, frame, filename, arg) + return self.trace_return + # IFDEF CYTHON # def should_stop_on_exception(self, frame, str event, arg): # cdef PyDBAdditionalThreadInfo info; @@ -274,12 +281,15 @@ class PyDBFrame: # No longer cdef because object was dying when only a reference try: # print 'frame trace_dispatch', frame.f_lineno, frame.f_code.co_name, event info.is_tracing = True + need_trace_return = False if main_debugger._finish_debugging_session: return None if event == 'call' and main_debugger.signature_factory: - send_signature_call_trace(main_debugger, frame, filename) + need_trace_return = send_signature_call_trace(main_debugger, frame, filename) + if event == 'return' and main_debugger.signature_factory: + send_signature_return_trace(main_debugger, frame, filename, arg) plugin_manager = main_debugger.plugin @@ -341,7 +351,10 @@ class PyDBFrame: # No longer cdef because object was dying when only a reference if has_exception_breakpoints: return self.trace_exception else: - return None + if need_trace_return: + return self.trace_return + else: + return None else: #checks the breakpoint to see if there is a context match in some function @@ -361,7 +374,10 @@ class PyDBFrame: # No longer cdef because object was dying when only a reference if has_exception_breakpoints: return self.trace_exception else: - return None + if need_trace_return: + return self.trace_return + else: + return None #We may have hit a breakpoint or we are already in step mode. Either way, let's check what we should do in this frame diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_signature.py b/python/helpers/pydev/_pydevd_bundle/pydevd_signature.py index e529994048d0..98b660f4ff51 100644 --- a/python/helpers/pydev/_pydevd_bundle/pydevd_signature.py +++ b/python/helpers/pydev/_pydevd_bundle/pydevd_signature.py @@ -19,6 +19,7 @@ class Signature(object): self.name = name self.args = [] self.args_str = [] + self.return_type = None def add_arg(self, name, type): self.args.append((name, type)) @@ -28,32 +29,39 @@ class Signature(object): return "%s %s(%s)"%(self.file, self.name, ", ".join(self.args_str)) +def get_type_of_value(value, ignore_module_name=('__main__', '__builtin__', 'builtins')): + tp = type(value) + class_name = tp.__name__ + if class_name == 'instance': # old-style classes + tp = value.__class__ + class_name = tp.__name__ + + if hasattr(tp, '__module__') and tp.__module__ and tp.__module__ not in ignore_module_name: + class_name = "%s.%s"%(tp.__module__, class_name) + + return class_name + + class SignatureFactory(object): def __init__(self): self._caller_cache = {} - self._ignore_module_name = ('__main__', '__builtin__', 'builtins') + self.cache = CallSignatureCache() def is_in_scope(self, filename): return not pydevd_utils.not_in_project_roots(filename) - def create_signature(self, frame): + def create_signature(self, frame, with_args=True): try: code = frame.f_code locals = frame.f_locals filename, modulename, funcname = self.file_module_function_of(frame) res = Signature(filename, funcname) - for i in xrange(0, code.co_argcount): - name = code.co_varnames[i] - tp = type(locals[name]) - class_name = tp.__name__ - if class_name == 'instance': # old-style classes - tp = locals[name].__class__ - class_name = tp.__name__ - - if hasattr(tp, '__module__') and tp.__module__ and tp.__module__ not in self._ignore_module_name: - class_name = "%s.%s"%(tp.__module__, class_name) - - res.add_arg(name, class_name) + if with_args: + for i in xrange(0, code.co_argcount): + name = code.co_varnames[i] + class_name = get_type_of_value(locals[name]) + + res.add_arg(name, class_name) return res except: import traceback @@ -109,6 +117,33 @@ class SignatureFactory(object): return filename, modulename, funcname + +def get_signature_info(signature): + return signature.file, signature.name, ' '.join([arg[1]for arg in signature.args]) + + +def get_frame_info(frame): + co = frame.f_code + return co.co_name, frame.f_lineno, co.co_filename + + +class CallSignatureCache(object): + def __init__(self): + self.cache = {} + + def add(self, signature): + filename, name, args_type = get_signature_info(signature) + calls_from_file = self.cache.setdefault(filename, {}) + name_calls = calls_from_file.setdefault(name, {}) + name_calls[args_type] = None + + def is_in_cache(self, signature): + filename, name, args_type = get_signature_info(signature) + if args_type in self.cache.get(filename, {}).get(name, {}): + return True + return False + + def create_signature_message(signature): cmdTextList = [""] @@ -116,14 +151,40 @@ def create_signature_message(signature): for arg in signature.args: cmdTextList.append('' % (pydevd_vars.make_valid_xml_value(arg[0]), pydevd_vars.make_valid_xml_value(arg[1]))) + + if signature.return_type is not None: + cmdTextList.append('' % (pydevd_vars.make_valid_xml_value(signature.return_type))) cmdTextList.append("") cmdText = ''.join(cmdTextList) return NetCommand(CMD_SIGNATURE_CALL_TRACE, 0, cmdText) + def send_signature_call_trace(dbg, frame, filename): - if dbg.signature_factory.is_in_scope(filename): - dbg.writer.add_command(create_signature_message(dbg.signature_factory.create_signature(frame))) + if dbg.signature_factory and dbg.signature_factory.is_in_scope(filename): + signature = dbg.signature_factory.create_signature(frame) + if dbg.signature_factory.cache is not None: + if not dbg.signature_factory.cache.is_in_cache(signature): + dbg.signature_factory.cache.add(signature) + dbg.writer.add_command(create_signature_message(signature)) + return True + else: + # we don't send signature if it is cached + return False + else: + dbg.writer.add_command(create_signature_message(signature)) + return True + return False + + +def send_signature_return_trace(dbg, frame, filename, return_value): + if dbg.signature_factory and dbg.signature_factory.is_in_scope(filename): + signature = dbg.signature_factory.create_signature(frame, with_args=False) + signature.return_type = get_type_of_value(return_value) + dbg.writer.add_command(create_signature_message(signature)) + return True + + return False diff --git a/python/ipnb/lib/batik.jar b/python/ipnb/lib/batik.jar deleted file mode 100644 index 589328581963..000000000000 Binary files a/python/ipnb/lib/batik.jar and /dev/null differ diff --git a/python/ipnb/lib/jeuclid-core-3.1.9.jar b/python/ipnb/lib/jeuclid-core-3.1.9.jar deleted file mode 100644 index d6d8d2d4c764..000000000000 Binary files a/python/ipnb/lib/jeuclid-core-3.1.9.jar and /dev/null differ diff --git a/python/ipnb/lib/snuggletex-core-1.3-SNAPSHOT.jar b/python/ipnb/lib/snuggletex-core-1.3-SNAPSHOT.jar deleted file mode 100644 index e5677881a08b..000000000000 Binary files a/python/ipnb/lib/snuggletex-core-1.3-SNAPSHOT.jar and /dev/null differ diff --git a/python/ipnb/lib/xmlgraphics-commons-1.3.1.jar b/python/ipnb/lib/xmlgraphics-commons-1.3.1.jar deleted file mode 100644 index 81a485dc4e6f..000000000000 Binary files a/python/ipnb/lib/xmlgraphics-commons-1.3.1.jar and /dev/null differ diff --git a/python/ipnb/resources/META-INF/ipython-notebook.xml b/python/ipnb/resources/META-INF/ipython-notebook.xml index a508d381e352..81f696690c8c 100644 --- a/python/ipnb/resources/META-INF/ipython-notebook.xml +++ b/python/ipnb/resources/META-INF/ipython-notebook.xml @@ -52,6 +52,10 @@ + + + + diff --git a/python/ipnb/resources/fileTemplates/internal/IPython Notebook.ipynb.ft b/python/ipnb/resources/fileTemplates/internal/IPython Notebook.ipynb.ft new file mode 100644 index 000000000000..01c10b25b305 --- /dev/null +++ b/python/ipnb/resources/fileTemplates/internal/IPython Notebook.ipynb.ft @@ -0,0 +1,34 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 2", + "language": "python", + "name": "python2" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/python/ipnb/resources/org/jetbrains/plugins/ipnb/math-characters.txt b/python/ipnb/resources/org/jetbrains/plugins/ipnb/math-characters.txt deleted file mode 100644 index 024394e18d7d..000000000000 --- a/python/ipnb/resources/org/jetbrains/plugins/ipnb/math-characters.txt +++ /dev/null @@ -1,4 +0,0 @@ -## This is hand-edited. Add all of the non-auto-generated definitions -3A6:varPhi:ALPHA -3C6:varphi:ALPHA -02248:thickapprox:REL \ No newline at end of file diff --git a/python/ipnb/resources/style/javaFXBrowserDarcula.css b/python/ipnb/resources/style/javaFXBrowserDarcula.css new file mode 100644 index 000000000000..b8039148afd0 --- /dev/null +++ b/python/ipnb/resources/style/javaFXBrowserDarcula.css @@ -0,0 +1,28 @@ +body { + background-color: #313335 !important; + color: #bababa !important; +} + +.MathJax_Display { + color: #bababa !important; + background-color: #313335 !important; +} + +#mydiv { + color: #bababa !important; + background-color: #313335 !important; +} + +.MathJax { + background-color: #313335 !important; + color: #bababa !important; +} + +a { + color: #589df6 !important; +} + +.MathJax_Preview { + color: #bababa !important; +} + diff --git a/python/ipnb/src/org/jetbrains/plugins/ipnb/IpnbUtils.java b/python/ipnb/src/org/jetbrains/plugins/ipnb/IpnbUtils.java index 84ed2d892b2e..604d4f248206 100644 --- a/python/ipnb/src/org/jetbrains/plugins/ipnb/IpnbUtils.java +++ b/python/ipnb/src/org/jetbrains/plugins/ipnb/IpnbUtils.java @@ -1,243 +1,271 @@ package org.jetbrains.plugins.ipnb; +import com.intellij.ide.BrowserUtil; +import com.intellij.ide.ui.LafManager; +import com.intellij.ide.ui.laf.darcula.DarculaLookAndFeelInfo; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.colors.EditorColorsManager; import com.intellij.openapi.util.text.StringUtil; -import com.intellij.ui.BrowserHyperlinkListener; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.text.MarkdownUtil; import com.intellij.util.ui.UIUtil; import com.petebevin.markdown.MarkdownProcessor; -import net.sourceforge.jeuclid.MathMLParserSupport; -import net.sourceforge.jeuclid.context.LayoutContextImpl; -import net.sourceforge.jeuclid.context.Parameter; -import net.sourceforge.jeuclid.converter.Converter; +import javafx.application.Platform; +import javafx.concurrent.Worker; +import javafx.embed.swing.JFXPanel; +import javafx.event.EventHandler; +import javafx.scene.Scene; +import javafx.scene.layout.BorderPane; +import javafx.scene.web.WebEngine; +import javafx.scene.web.WebEvent; +import javafx.scene.web.WebView; +import netscape.javascript.JSException; import org.jetbrains.annotations.NotNull; import org.jetbrains.plugins.ipnb.editor.IpnbEditorUtil; -import org.jetbrains.plugins.ipnb.editor.panels.IpnbTexPackageDefinitions; import org.w3c.dom.Document; -import org.xml.sax.SAXException; -import uk.ac.ed.ph.snuggletex.SnuggleEngine; -import uk.ac.ed.ph.snuggletex.SnuggleInput; -import uk.ac.ed.ph.snuggletex.SnuggleSession; -import uk.ac.ed.ph.snuggletex.XMLStringOutputOptions; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.w3c.dom.events.Event; +import org.w3c.dom.events.EventListener; +import org.w3c.dom.events.EventTarget; import javax.swing.*; -import javax.swing.text.html.HTMLDocument; -import javax.swing.text.html.HTMLEditorKit; -import javax.swing.text.html.StyleSheet; -import javax.xml.parsers.ParserConfigurationException; import java.awt.*; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; -import java.awt.image.BufferedImage; -import java.io.IOException; -import java.net.MalformedURLException; +import java.awt.event.MouseWheelEvent; +import java.net.URI; +import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; -import java.util.Dictionary; -import java.util.Hashtable; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class IpnbUtils { private static final Logger LOG = Logger.getInstance(IpnbUtils.class); + private static final String ourPrefix = "
"; + + private static final String ourPostfix = "
"; private static final MarkdownProcessor ourMarkdownProcessor = new MarkdownProcessor(); - private static final String ourImagePrefix = "http://image"; - private static final Font ourFont = new Font(Font.SERIF, Font.PLAIN, 16); - private static final String ourBodyRule = "body { font-family: \"DejaVu\"; " + - "font-size: " + ourFont.getSize() + "pt;}"; + private static URL ourStyleUrl; - private static final String ourCodeRule = "code { font-family: \"DejaVu\"; " + - "font-size: " + ourFont.getSize() + "pt;}"; + public static JComponent createLatexPane(@NotNull final String source, int width) { + final JComponent panel = createHtmlPanel(source, width); - private static final String ourAlertRule = ".alert{ background-color: #fcf8e3;" + - "color: #c09853;" + - "padding: 8px 35px 8px 14px;" + - "border: 1px solid #fbeed5;}"; - - private static final String ourAlertSuccessRule = ".alert-success{ background-color: #dff0d8;\n" + - "color: #468847;" + - "padding: 8px 35px 8px 14px;" + - "border: 1px solid #d6e9c6;}"; - - private static final String ourAlertErrorRule = ".alert-error{ background-color: #f2dede;\n" + - "color: #b94a48;" + - "padding: 8px 35px 8px 14px;" + - "border: 1px solid #eed3d7;}"; - - public static String markdown2Html(@NotNull String description) { - description = StringUtil.replace(description, "class=\"alert alert-success\"", "class=\"alert-success\""); - description = StringUtil.replace(description, "class=\"alert alert-error\"", "class=\"alert-error\""); - ArrayList lines = ContainerUtil.newArrayList(description.split("\n|\r|\r\n")); - MarkdownUtil.replaceHeaders(lines); - return ourMarkdownProcessor.markdown(StringUtil.join(lines, "\n")); - } - - public static JEditorPane createLatexPane(@NotNull final String source) { - final JEditorPane editorPane = new JEditorPane(); - editorPane.setBackground(IpnbEditorUtil.getBackground()); - editorPane.setContentType(new HTMLEditorKit().getContentType()); - editorPane.setEditorKit(new HTMLEditorKit()); - final HTMLDocument document = (HTMLDocument)editorPane.getDocument(); - final StyleSheet sheet = document.getStyleSheet(); - sheet.addRule(ourBodyRule); - sheet.addRule(ourCodeRule); - sheet.addRule(ourAlertRule); - sheet.addRule(ourAlertSuccessRule); - sheet.addRule(ourAlertErrorRule); - - editorPane.setEditable(false); - - final String html = convertToHtml(source, document); - editorPane.setText("" + html + ""); - - editorPane.addMouseListener(new MouseAdapter() { + panel.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { - final Container parent = editorPane.getParent(); - final MouseEvent parentEvent = SwingUtilities.convertMouseEvent(editorPane, e, parent); + final Container parent = panel.getParent(); + final MouseEvent parentEvent = SwingUtilities.convertMouseEvent(panel, e, parent); parent.dispatchEvent(parentEvent); } }); - editorPane.addHyperlinkListener(new BrowserHyperlinkListener()); //TODO: jump to the section (see User Interface#Utilities) - return editorPane; + return panel; } + public static JComponent createHtmlPanel(@NotNull final String source, int width) { + Platform.setImplicitExit(false); - private static String convertToHtml(@NotNull final String source, @NotNull final HTMLDocument document) { + final JFXPanel javafxPanel = new JFXPanel(){ + @Override + protected void processMouseWheelEvent(MouseWheelEvent e) { + final Container parent = getParent(); + final MouseEvent parentEvent = SwingUtilities.convertMouseEvent(this, e, parent); + parent.dispatchEvent(parentEvent); + } + }; + javafxPanel.setBackground(IpnbEditorUtil.getBackground()); + + Platform.runLater(() -> { + final WebView webView = new WebView(); + webView.setOnDragDetected(new EventHandler() { + @Override + public void handle(javafx.scene.input.MouseEvent event) { + } + }); + final WebEngine engine = webView.getEngine(); + initHyperlinkListener(engine); + engine.setOnStatusChanged(new EventHandler>() { + public void handle(WebEvent status) { + adjustHeight(webView, javafxPanel, source); + } + }); + + final String prefix = String.format(ourPrefix, EditorColorsManager.getInstance().getGlobalScheme().getEditorFontSize()); + engine.loadContent(prefix + convertToHtml(source) + ourPostfix); + final BorderPane pane = new BorderPane(webView); + final Scene scene = new Scene(pane, width != 0 ? width : 20, 20); + javafxPanel.setScene(scene); + Platform.runLater(() -> adjustHeight(webView, javafxPanel, source)); + updateLaf(LafManager.getInstance().getCurrentLookAndFeel() instanceof DarculaLookAndFeelInfo, + pane, engine, javafxPanel); + }); + + return javafxPanel; + } + + private static String convertToHtml(@NotNull String source) { + source = StringUtil.replace(source, "class=\"alert alert-success\"", "class=\"alert-success\""); + source = StringUtil.replace(source, "class=\"alert alert-error\"", "class=\"alert-error\""); + ArrayList lines = ContainerUtil.newArrayList(source.split("\n|\r|\r\n")); + + MarkdownUtil.replaceHeaders(lines); + source = StringUtil.join(lines, "\n"); final StringBuilder result = new StringBuilder(); - StringBuilder markdown = new StringBuilder(); - StringBuilder formula = new StringBuilder(); - boolean inCode = false; - int inMultiStringCode = 0; - boolean inEnd = false; + + source = replaceLinks(source); + source = ourMarkdownProcessor.markdown(source); + boolean escaped = false; - boolean backQuoted = false; - - int imageIndex = 0; - for (int i = 0; i != source.length(); ++i) { - final char charAt = source.charAt(i); - - if (charAt == '`') { - backQuoted = !backQuoted; - if (source.length() > i + 2 && source.charAt(i + 1) == '`' && source.charAt(i + 2) == '`') { - markdown.append(escaped ? "" : ""); - escaped = !escaped; - //noinspection AssignmentToForLoopParameter - i += 2; - continue; - } - } - if (escaped && charAt == '\n') { - markdown.append("
"); - continue; - } - - if (!escaped && !backQuoted) { - if (charAt == '$' && source.length() > i + 1 && source.charAt(i+1) != '$') { - inCode = !inCode; - } - - if (charAt == '\\' && source.substring(i).startsWith("\\begin")) { - inMultiStringCode += 1; - } - if (charAt == '\\' && source.substring(i).startsWith("\\end")) { - inEnd = true; - } - } - - final boolean doubleDollar = charAt == '$' && ((source.length() > i + 1 && source.charAt(i + 1) == '$') - || (i >= 1 && source.charAt(i - 1) == '$')); - if (inCode || inMultiStringCode != 0 || (!backQuoted && doubleDollar)) { - if (markdown.length() != 0) { - result.append(markdown.toString()); - markdown = new StringBuilder(); - } - formula.append(charAt); - } - else { - if (formula.length() != 0 && charAt == '$') { - formula.append(charAt); - } - else { - markdown.append(charAt); - } - if (formula.length() != 0) { - addFormula(formula.toString(), document, imageIndex, result); - - imageIndex += 1; - formula = new StringBuilder(); - } - } - - if (inEnd && charAt == '}') { - inMultiStringCode -= 1; - inEnd = false; - } - + int start = 0; + int end = StringUtil.indexOf(source, "```"); + while (end > 0) { + result.append(source.substring(start, end)); + result.append(escaped? "" : "
");
+      escaped = !escaped;
+      start = end + 3;
+      end = StringUtil.indexOf(source, "```", end + 1);
     }
-    if (formula.length() != 0) {
-      addFormula(formula.toString(), document, imageIndex, result);
-    }
-    if (markdown.length() != 0) {
-      result.append(markdown.toString());
-    }
-    return markdown2Html(result.toString());
+    result.append(source.substring(start));
+
+    return result.toString();
   }
 
-  private static void addFormula(@NotNull final String formulaText, @NotNull final HTMLDocument editorDocument, int imageIndex, StringBuilder result) {
-    final SnuggleEngine engine = new SnuggleEngine();
-    engine.getPackages().add(0, IpnbTexPackageDefinitions.getPackage());
+  @NotNull
+  private static String replaceLinks(@NotNull String source) {
+    final Pattern inlineLink = Pattern.compile("(\\[(.*?)\\]\\([ \\t]*?[ \\t]*(([\'\"])(.*?)\\5)?\\))", 32);
+    final Matcher matcher = inlineLink.matcher(source);
+    final StringBuffer sb = new StringBuffer();
+    while (matcher.find()) {
+      String linkText = matcher.group(2);
+      String url = matcher.group(3);
+      String title = matcher.group(6);
+      StringBuilder link = new StringBuilder();
+      link.append("").append(linkText);
+      link.append("");
+      matcher.appendReplacement(sb, link.toString());
+    }
+    matcher.appendTail(sb);
 
-    final SnuggleInput input = new SnuggleInput(formulaText);
+    source = sb.toString();
+    return source;
+  }
 
+  private static void initHyperlinkListener(@NotNull final WebEngine engine) {
+    engine.getLoadWorker().stateProperty().addListener((ov, oldState, newState) -> {
+      if (newState == Worker.State.SUCCEEDED) {
+        final EventListener listener = new HyperlinkListener(engine);
+        addListenerToAllHyperlinkItems(engine, listener);
+      }
+    });
+  }
+
+  private static void addListenerToAllHyperlinkItems(WebEngine engine, EventListener listener) {
+    final Document doc = engine.getDocument();
+    if (doc != null) {
+      final NodeList nodeList = doc.getElementsByTagName("a");
+      for (int i = 0; i < nodeList.getLength(); i++) {
+        ((EventTarget)nodeList.item(i)).addEventListener("click", listener, false);
+      }
+    }
+  }
+
+  private static class HyperlinkListener implements EventListener {
+    @NotNull private final WebEngine myEngine;
+
+    public HyperlinkListener(@NotNull final WebEngine engine) {
+      myEngine = engine;
+    }
+
+    @Override
+    public void handleEvent(Event ev) {
+      String domEventType = ev.getType();
+      if (domEventType.equals("click")) {
+        myEngine.setJavaScriptEnabled(true);
+        myEngine.getLoadWorker().cancel();
+        ev.preventDefault();
+
+        UIUtil.invokeLaterIfNeeded(() -> {
+
+          final String href = ((Element)ev.getTarget()).getAttribute("href");
+          if (href == null) return;
+          final URI address;
+          try {
+            address = new URI(href);
+            BrowserUtil.browse(address);
+          }
+          catch (URISyntaxException e) {
+            LOG.warn(e.getMessage());
+          }
+        });
+
+      }
+    }
+  }
+
+  private static void adjustHeight(final WebView webView, final JFXPanel javafxPanel, String source) {
     try {
-      session.parseInput(input);
-      XMLStringOutputOptions options = new XMLStringOutputOptions();
-      options.setIndenting(true);
-      options.setAddingMathSourceAnnotations(false);
-      final String xmlString = session.buildXMLString(options);
-      if (xmlString == null) return;
-      final LayoutContextImpl context = (LayoutContextImpl)LayoutContextImpl.getDefaultLayoutContext();
-      context.setParameter(Parameter.MATHCOLOR, UIUtil.getTextAreaForeground());
-      context.setParameter(Parameter.MATHSIZE, 18);
+      Object result = webView.getEngine().executeScript("document.getElementById(\"mydiv\").offsetHeight");
+      if (result instanceof Integer) {
+        final int fontSize = EditorColorsManager.getInstance().getGlobalScheme().getEditorFontSize();
+        double x = (double)source.length() * 8 / (int)result;
+        final double height = (source.length() * fontSize) / x + 20;
+        final int width = (int)(webView.getWidth() == 0 ? 1500 : webView.getWidth());
+        final Dimension size = new Dimension(width, (int)height);
 
-      final Document document = MathMLParserSupport.parseString(xmlString);
-
-      final BufferedImage image = Converter.getInstance().render(document, context);
-
-      try {
-        @SuppressWarnings("unchecked")
-        Dictionary cache = (Dictionary)editorDocument.getProperty("imageCache");
-        if (cache == null) {
-          //noinspection UseOfObsoleteCollectionType
-          cache = new Hashtable();
-          editorDocument.putProperty("imageCache", cache);
-        }
-
-        final URL u = new URL(ourImagePrefix + imageIndex + ".jpg");
-        cache.put(u, image);
-      }
-      catch (MalformedURLException e) {
-        LOG.error(e);
+        UIUtil.invokeLaterIfNeeded(new Runnable() {
+          @Override
+          public void run() {
+            javafxPanel.setPreferredSize(size);
+            javafxPanel.revalidate();
+            javafxPanel.repaint();
+          }
+        });
       }
     }
-    catch (IOException e) {
-      LOG.error(e);
+    catch (JSException ignore) {
     }
-    catch (SAXException e) {
-      LOG.error(e);
-      LOG.info("formula text " + formulaText);
+  }
+
+  private static void updateLaf(boolean isDarcula, BorderPane pane, WebEngine engine, JFXPanel jfxPanel) {
+    if (isDarcula) {
+      updateLafDarcula(pane, engine, jfxPanel);
     }
-    catch (ParserConfigurationException e) {
-      LOG.error(e);
-    }
-    if (formulaText.startsWith("$$") || formulaText.startsWith("\\begin")) {
-      result.append("

"); - } - else - result.append(""); + } + + private static void updateLafDarcula(BorderPane pane, WebEngine engine, JFXPanel jfxPanel) { + Platform.runLater(() -> { + ourStyleUrl = IpnbFileType.class.getResource("/style/javaFXBrowserDarcula.css"); + engine.setUserStyleSheetLocation(ourStyleUrl.toExternalForm()); + pane.setStyle("-fx-background-color: #313335"); + jfxPanel.getScene().getStylesheets().add(ourStyleUrl.toExternalForm()); + engine.reload(); + }); } } diff --git a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/IpnbFileEditor.java b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/IpnbFileEditor.java index b72dd2c1403d..c392c7f0786d 100644 --- a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/IpnbFileEditor.java +++ b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/IpnbFileEditor.java @@ -183,7 +183,7 @@ public class IpnbFileEditor extends UserDataHolderBase implements FileEditor { public void actionPerformed(ActionEvent e) { IpnbReloadKernelAction.reloadKernel(IpnbFileEditor.this); } - }, AllIcons.Actions.Refresh, "Restart kernel"); + }, AllIcons.Actions.Refresh, "Restart Kernel"); } private void addSaveButton(@NotNull final JPanel controlPanel) { diff --git a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/actions/IpnbCreateFileAction.java b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/actions/IpnbCreateFileAction.java new file mode 100644 index 000000000000..189ee133571d --- /dev/null +++ b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/actions/IpnbCreateFileAction.java @@ -0,0 +1,41 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.plugins.ipnb.editor.actions; + +import com.intellij.ide.actions.CreateFileFromTemplateAction; +import com.intellij.ide.actions.CreateFileFromTemplateDialog; +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiDirectory; +import org.jetbrains.plugins.ipnb.IpnbFileType; + +public class IpnbCreateFileAction extends CreateFileFromTemplateAction implements DumbAware { + public IpnbCreateFileAction() { + super("IPython Notebook", "Creates an IPython Notebook file from the specified template", IpnbFileType.INSTANCE.getIcon()); + } + + @Override + protected void buildDialog(Project project, PsiDirectory directory, CreateFileFromTemplateDialog.Builder builder) { + builder + .setTitle("New IPython Notebook") + .addKind("IPython Notebook", IpnbFileType.INSTANCE.getIcon(), "IPython Notebook"); + } + + @Override + protected String getActionName(PsiDirectory directory, String newName, String templateName) { + return "Create IPython Notebook " + newName; + } +} diff --git a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/panels/IpnbFilePanel.java b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/panels/IpnbFilePanel.java index 0fd8853ed919..fb0a77a7ab44 100644 --- a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/panels/IpnbFilePanel.java +++ b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/panels/IpnbFilePanel.java @@ -162,7 +162,7 @@ public class IpnbFilePanel extends JPanel implements Scrollable, DataProvider, D myIpnbPanels.add(panel); } else if (cell instanceof IpnbMarkdownCell) { - panel = new IpnbMarkdownPanel((IpnbMarkdownCell)cell); + panel = new IpnbMarkdownPanel((IpnbMarkdownCell)cell, this); addComponent(panel); } else if (cell instanceof IpnbHeadingCell) { @@ -369,7 +369,7 @@ public class IpnbFilePanel extends JPanel implements Scrollable, DataProvider, D panel = new IpnbCodePanel(myProject, myParent, (IpnbCodeCell)cell); } else if (cell instanceof IpnbMarkdownCell) { - panel = new IpnbMarkdownPanel((IpnbMarkdownCell)cell); + panel = new IpnbMarkdownPanel((IpnbMarkdownCell)cell, myParent.getIpnbFilePanel()); } else if (cell instanceof IpnbHeadingCell) { panel = new IpnbHeadingPanel((IpnbHeadingCell)cell); diff --git a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/panels/IpnbHeadingPanel.java b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/panels/IpnbHeadingPanel.java index 38c5cc4512f0..b5874d4a72ce 100644 --- a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/panels/IpnbHeadingPanel.java +++ b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/panels/IpnbHeadingPanel.java @@ -1,5 +1,6 @@ package org.jetbrains.plugins.ipnb.editor.panels; +import com.intellij.openapi.editor.colors.EditorColorsManager; import com.intellij.ui.components.JBLabel; import org.jetbrains.annotations.NotNull; import org.jetbrains.plugins.ipnb.editor.IpnbEditorUtil; @@ -18,7 +19,8 @@ public class IpnbHeadingPanel extends IpnbEditablePanel" + myCell.getSourceAsString() + "" + myCell.getSourceAsString() + ""; } diff --git a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/panels/IpnbMarkdownPanel.java b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/panels/IpnbMarkdownPanel.java index 918b22a2e08c..c462a6a2b24b 100644 --- a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/panels/IpnbMarkdownPanel.java +++ b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/panels/IpnbMarkdownPanel.java @@ -6,10 +6,13 @@ import org.jetbrains.plugins.ipnb.format.cells.IpnbMarkdownCell; import javax.swing.*; -public class IpnbMarkdownPanel extends IpnbEditablePanel { +public class IpnbMarkdownPanel extends IpnbEditablePanel { - public IpnbMarkdownPanel(@NotNull final IpnbMarkdownCell cell) { + private final IpnbFilePanel myParent; + + public IpnbMarkdownPanel(@NotNull final IpnbMarkdownCell cell, @NotNull final IpnbFilePanel parent) { super(cell); + myParent = parent; initPanel(); } @@ -19,8 +22,9 @@ public class IpnbMarkdownPanel extends IpnbEditablePanel content = token.getArguments()[0].getContents(); - Element result = builder.appendMathMLElement(parentElement, "mover"); - builder.handleMathTokensAsSingleElement(result, content); - builder.appendMathMLOperatorElement(result, "\u02D9"); - } - }, null); - ourPackage.addComplexCommandSameArgMode("pi", false, 0, MATH_MODE_ONLY, new CommandHandler() { - @Override - public void handleCommand(DOMBuilder builder, Element parentElement, CommandToken token) throws SnuggleParseException { - builder.appendMathMLOperatorElement(parentElement, "\u03c0"); - } - }, null); - ourPackage.addEnvironment("eqnarray", ALL_MODES, MATH, Interpretation.TABULAR, new EqnArrayHandler(), START_NEW_XHTML_BLOCK); - ourPackage.addSimpleMathCommand("lt", new MathOperatorInterpretation("<")); - ourPackage.addSimpleMathCommand("gt", new MathOperatorInterpretation(">")); - - ourPackage.loadMathCharacterDefinitions(ourMathCharacters); - } -} diff --git a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/panels/code/IpnbCodeOutputPanel.java b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/panels/code/IpnbCodeOutputPanel.java index 4b65051a71bd..1bf847c81c6f 100644 --- a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/panels/code/IpnbCodeOutputPanel.java +++ b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/panels/code/IpnbCodeOutputPanel.java @@ -1,7 +1,10 @@ package org.jetbrains.plugins.ipnb.editor.panels.code; +import com.intellij.openapi.editor.colors.EditorColorsManager; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.ipnb.editor.IpnbEditorUtil; +import org.jetbrains.plugins.ipnb.editor.panels.IpnbFilePanel; import org.jetbrains.plugins.ipnb.editor.panels.IpnbPanel; import org.jetbrains.plugins.ipnb.format.cells.output.IpnbOutputCell; @@ -9,9 +12,11 @@ import javax.swing.*; import java.awt.*; public class IpnbCodeOutputPanel extends IpnbPanel { + protected final IpnbFilePanel myParent; - public IpnbCodeOutputPanel(@NotNull final K cell) { + public IpnbCodeOutputPanel(@NotNull final K cell, @Nullable final IpnbFilePanel parent) { super(cell, new BorderLayout()); + myParent = parent; myViewPanel = createViewPanel(); add(myViewPanel); } @@ -20,6 +25,9 @@ public class IpnbCodeOutputPanel extends IpnbPanel { } else if (outputCell instanceof IpnbHtmlOutputCell) { addPromptPanel(panel, myCell.getPromptNumber(), promptType, - new IpnbHtmlPanel((IpnbHtmlOutputCell)outputCell)); + new IpnbHtmlPanel((IpnbHtmlOutputCell)outputCell, myParent.getIpnbFilePanel())); } else if (outputCell instanceof IpnbLatexOutputCell) { addPromptPanel(panel, myCell.getPromptNumber(), promptType, - new IpnbLatexPanel((IpnbLatexOutputCell)outputCell)); + new IpnbLatexPanel((IpnbLatexOutputCell)outputCell, myParent.getIpnbFilePanel())); } else if (outputCell instanceof IpnbErrorOutputCell) { addPromptPanel(panel, myCell.getPromptNumber(), promptType, @@ -101,7 +101,7 @@ public class IpnbCodePanel extends IpnbEditablePanel { } else if (outputCell.getSourceAsString() != null) { addPromptPanel(panel, myCell.getPromptNumber(), promptType, - new IpnbCodeOutputPanel(outputCell)); + new IpnbCodeOutputPanel(outputCell, myParent.getIpnbFilePanel())); } mainPanel.add(panel); } diff --git a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/panels/code/IpnbErrorPanel.java b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/panels/code/IpnbErrorPanel.java index fca3dd12eca4..34a5407aa539 100644 --- a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/panels/code/IpnbErrorPanel.java +++ b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/panels/code/IpnbErrorPanel.java @@ -1,5 +1,6 @@ package org.jetbrains.plugins.ipnb.editor.panels.code; +import com.intellij.openapi.editor.colors.EditorColorsManager; import com.intellij.openapi.util.text.StringUtil; import com.intellij.ui.JBColor; import org.jetbrains.annotations.NotNull; @@ -16,7 +17,7 @@ import java.util.List; public class IpnbErrorPanel extends IpnbCodeOutputPanel { public IpnbErrorPanel(@NotNull final IpnbErrorOutputCell cell) { - super(cell); + super(cell, null); } @Override @@ -24,6 +25,9 @@ public class IpnbErrorPanel extends IpnbCodeOutputPanel { final List text = myCell.getText(); if (text == null) return new JLabel(); ColorPane ansiColoredPane = new ColorPane(); + final Font font = ansiColoredPane.getFont(); + final Font newFont = new Font(font.getName(), font.getStyle(), EditorColorsManager.getInstance().getGlobalScheme().getEditorFontSize()); + ansiColoredPane.setFont(newFont); ansiColoredPane.appendANSI(StringUtil.join(text, "")); ansiColoredPane.setBackground(IpnbEditorUtil.getBackground()); ansiColoredPane.setEditable(false); diff --git a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/panels/code/IpnbHtmlPanel.java b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/panels/code/IpnbHtmlPanel.java index 1e10546a9002..afce5d4567bf 100644 --- a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/panels/code/IpnbHtmlPanel.java +++ b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/panels/code/IpnbHtmlPanel.java @@ -1,29 +1,22 @@ package org.jetbrains.plugins.ipnb.editor.panels.code; -import com.intellij.ui.components.JBLabel; +import com.intellij.openapi.util.text.StringUtil; import org.jetbrains.annotations.NotNull; -import org.jetbrains.plugins.ipnb.editor.IpnbEditorUtil; +import org.jetbrains.plugins.ipnb.IpnbUtils; +import org.jetbrains.plugins.ipnb.editor.panels.IpnbFilePanel; import org.jetbrains.plugins.ipnb.format.cells.output.IpnbHtmlOutputCell; import javax.swing.*; public class IpnbHtmlPanel extends IpnbCodeOutputPanel { - public IpnbHtmlPanel(@NotNull final IpnbHtmlOutputCell cell) { - super(cell); + + public IpnbHtmlPanel(@NotNull final IpnbHtmlOutputCell cell, @NotNull final IpnbFilePanel parent) { + super(cell, parent); } @Override protected JComponent createViewPanel() { - final StringBuilder text = new StringBuilder(""); - for (String html : myCell.getHtmls()) { - html = html.replace("\"", "'"); - text.append(html); - } - text.append(""); - - final JBLabel label = new JBLabel(text.toString()); - label.setBackground(IpnbEditorUtil.getBackground()); - label.setOpaque(true); - return label; + final int width = myParent.getWidth(); + return IpnbUtils.createLatexPane(StringUtil.join(myCell.getHtmls(), ""), width); } } diff --git a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/panels/code/IpnbImagePanel.java b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/panels/code/IpnbImagePanel.java index c57528359fa8..efd115cb9caf 100644 --- a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/panels/code/IpnbImagePanel.java +++ b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/panels/code/IpnbImagePanel.java @@ -3,10 +3,10 @@ package org.jetbrains.plugins.ipnb.editor.panels.code; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.text.StringUtil; import com.intellij.ui.components.JBLabel; -import com.intellij.util.Base64; import org.jetbrains.annotations.NotNull; import org.jetbrains.plugins.ipnb.editor.IpnbEditorUtil; import org.jetbrains.plugins.ipnb.format.cells.output.IpnbImageOutputCell; +import sun.misc.BASE64Decoder; import javax.imageio.ImageIO; import javax.swing.*; @@ -17,7 +17,7 @@ public class IpnbImagePanel extends IpnbCodeOutputPanel { private static final Logger LOG = Logger.getInstance(IpnbImagePanel.class); public IpnbImagePanel(@NotNull final IpnbImageOutputCell cell) { - super(cell); + super(cell, null); } @Override @@ -27,7 +27,7 @@ public class IpnbImagePanel extends IpnbCodeOutputPanel { final JBLabel label = new JBLabel(); if (!StringUtil.isEmptyOrSpaces(png)) { try { - byte[] btDataFile = Base64.decode(png); + byte[] btDataFile = new BASE64Decoder().decodeBuffer(png); BufferedImage image = ImageIO.read(new ByteArrayInputStream(btDataFile)); label.setIcon(new ImageIcon(image)); } @@ -41,4 +41,4 @@ public class IpnbImagePanel extends IpnbCodeOutputPanel { return label; } -} \ No newline at end of file +} diff --git a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/panels/code/IpnbLatexPanel.java b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/panels/code/IpnbLatexPanel.java index a8eab10e9a93..c8dbe9044c4d 100644 --- a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/panels/code/IpnbLatexPanel.java +++ b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/panels/code/IpnbLatexPanel.java @@ -5,19 +5,22 @@ import com.intellij.openapi.util.text.StringUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.plugins.ipnb.IpnbUtils; import org.jetbrains.plugins.ipnb.editor.IpnbEditorUtil; +import org.jetbrains.plugins.ipnb.editor.panels.IpnbFilePanel; import org.jetbrains.plugins.ipnb.format.cells.output.IpnbLatexOutputCell; import javax.swing.*; public class IpnbLatexPanel extends IpnbCodeOutputPanel { - public IpnbLatexPanel(@NotNull final IpnbLatexOutputCell cell) { - super(cell); + + public IpnbLatexPanel(@NotNull final IpnbLatexOutputCell cell, @NotNull final IpnbFilePanel parent) { + super(cell, parent); setLayout(new VerticalFlowLayout(VerticalFlowLayout.TOP)); setBackground(IpnbEditorUtil.getBackground()); } @Override protected JComponent createViewPanel() { - return IpnbUtils.createLatexPane(StringUtil.join(myCell.getLatex(), "")); + final int width = myParent.getWidth(); + return IpnbUtils.createLatexPane(StringUtil.join(myCell.getLatex(), ""), width); } } diff --git a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/panels/code/IpnbStreamPanel.java b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/panels/code/IpnbStreamPanel.java index 06f2c8d0d7ef..6b068f5e0dfe 100644 --- a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/panels/code/IpnbStreamPanel.java +++ b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/panels/code/IpnbStreamPanel.java @@ -8,7 +8,7 @@ import javax.swing.*; public class IpnbStreamPanel extends IpnbCodeOutputPanel { public IpnbStreamPanel(@NotNull final IpnbStreamOutputCell cell) { - super(cell); + super(cell, null); } @Override diff --git a/python/pydevSrc/com/jetbrains/python/debugger/PySignature.java b/python/pydevSrc/com/jetbrains/python/debugger/PySignature.java index a75887aba711..9d13e6421554 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/PySignature.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/PySignature.java @@ -13,6 +13,8 @@ import java.util.List; public class PySignature { private final String myFile; private final String myFunctionName; + + private NamedParameter myReturnType = null; private final List myArgs = Lists.newArrayList(); @@ -46,6 +48,22 @@ public class PySignature { return myArgs; } + public NamedParameter getReturnType() { + return myReturnType; + } + + public PySignature addReturnType(@Nullable String returnType) { + if (StringUtil.isNotEmpty(returnType)) { + if (myReturnType != null) { + myReturnType.addType(returnType); + } + else { + myReturnType = new NamedParameter("", returnType); + } + } + return this; + } + @NotNull public PySignature addAllArgs(@NotNull PySignature signature) { for (NamedParameter param : signature.getArgs()) { @@ -71,6 +89,11 @@ public class PySignature { return null; } + @Nullable + public String getReturnTypeQualifiedName() { + return myReturnType != null ? myReturnType.getTypeQualifiedName() : null; + } + public static class NamedParameter { private final String myName; diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/AbstractCommand.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/AbstractCommand.java index 586d150caf79..94e4adb12a41 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/AbstractCommand.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/AbstractCommand.java @@ -30,6 +30,7 @@ public abstract class AbstractCommand { public static final int LOAD_SOURCE = 124; public static final int SMART_STEP_INTO = 128; public static final int EXIT = 129; + public static final int CALL_SIGNATURE_TRACE = 130; public static final int CMD_SET_PY_EXCEPTION = 131; @@ -45,6 +46,9 @@ public abstract class AbstractCommand { public static final int CMD_ENABLE_DONT_TRACE = 141; public static final int SHOW_CONSOLE = 142; + public static final int GET_ARRAY = 143; + public static final int STEP_INTO_MY_CODE = 144; + public static final int LOG_CONCURRENCY_EVENT = 145; public static final int ERROR = 901; @@ -52,9 +56,7 @@ public abstract class AbstractCommand { public static final String NEW_LINE_CHAR = "@_@NEW_LINE_CHAR@_@"; public static final String TAB_CHAR = "@_@TAB_CHAR@_@"; - public static final int GET_ARRAY = 143; - public static final int STEP_INTO_MY_CODE = 144; - public static final int LOG_CONCURRENCY_EVENT = 145; + @NotNull private final RemoteDebugger myDebugger; private final int myCommandCode; diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProtocolParser.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProtocolParser.java index cbfe858b2dad..ea4d5173b31b 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProtocolParser.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProtocolParser.java @@ -31,10 +31,15 @@ public class ProtocolParser { while (reader.hasMoreChildren()) { reader.moveDown(); - if (!"arg".equals(reader.getNodeName())) { - throw new PyDebuggerException("Expected , found " + reader.getNodeName()); + if ("arg".equals(reader.getNodeName())) { + signature.addArgument(readString(reader, "name", ""), readString(reader, "type", "")); + } else + if ("return".equals(reader.getNodeName())) { + signature.addReturnType(readString(reader, "type", "")); + } else { + throw new PyDebuggerException("Expected or , found " + reader.getNodeName()); } - signature.addArgument(readString(reader, "name", ""), readString(reader, "type", "")); + reader.moveUp(); } diff --git a/python/resources/inspectionDescriptions/PyMissingTypeHintsInspection.html b/python/resources/inspectionDescriptions/PyMissingTypeHintsInspection.html new file mode 100644 index 000000000000..7d88ef248c2c --- /dev/null +++ b/python/resources/inspectionDescriptions/PyMissingTypeHintsInspection.html @@ -0,0 +1,7 @@ + + + +This inspection detects lack of type hints for function declaration in + one of the two formats: parameter annotations or a type comment + + \ No newline at end of file diff --git a/python/resources/intentionDescriptions/PyAnnotateTypesIntention/after.html.template b/python/resources/intentionDescriptions/PyAnnotateTypesIntention/after.html.template new file mode 100644 index 000000000000..19a3444d8d77 --- /dev/null +++ b/python/resources/intentionDescriptions/PyAnnotateTypesIntention/after.html.template @@ -0,0 +1,2 @@ +def foo(var: object) -> object: + pass \ No newline at end of file diff --git a/python/resources/intentionDescriptions/PyAnnotateTypesIntention/before.html.template b/python/resources/intentionDescriptions/PyAnnotateTypesIntention/before.html.template new file mode 100644 index 000000000000..12d4a09279d0 --- /dev/null +++ b/python/resources/intentionDescriptions/PyAnnotateTypesIntention/before.html.template @@ -0,0 +1,2 @@ +def foo(var): + pass \ No newline at end of file diff --git a/python/resources/intentionDescriptions/PyAnnotateTypesIntention/description.html b/python/resources/intentionDescriptions/PyAnnotateTypesIntention/description.html new file mode 100644 index 000000000000..df913398c765 --- /dev/null +++ b/python/resources/intentionDescriptions/PyAnnotateTypesIntention/description.html @@ -0,0 +1,9 @@ + + + + This intention used to specify annotations for parameters and return type of a function. +

+ If there is type information collected in run-time, it is used to set the default values of types. + + + \ No newline at end of file diff --git a/python/src/META-INF/python-core-common.xml b/python/src/META-INF/python-core-common.xml index 753f27d8b397..673aa19d6801 100644 --- a/python/src/META-INF/python-core-common.xml +++ b/python/src/META-INF/python-core-common.xml @@ -185,6 +185,11 @@ Python + + com.jetbrains.python.codeInsight.intentions.PyAnnotateTypesIntention + Python + + com.jetbrains.python.codeInsight.intentions.TypeAssertionIntention Python @@ -375,6 +380,8 @@ + + diff --git a/python/src/com/jetbrains/python/PyBundle.properties b/python/src/com/jetbrains/python/PyBundle.properties index 51fd13b7e041..1c97f3f02175 100644 --- a/python/src/com/jetbrains/python/PyBundle.properties +++ b/python/src/com/jetbrains/python/PyBundle.properties @@ -258,6 +258,9 @@ INTN.add.parameters.to.docstring=Add parameters to docstring INTN.specify.type.in.annotation=Specify type for reference using annotation INTN.specify.return.type.in.annotation=Specify return type using annotation +#PyAnnotateTypesIntention +INTN.annotate.types=Annotate types + #TypeAssertionIntention INTN.insert.assertion=Insert type assertion diff --git a/python/src/com/jetbrains/python/codeInsight/intentions/PyAnnotateTypesIntention.java b/python/src/com/jetbrains/python/codeInsight/intentions/PyAnnotateTypesIntention.java new file mode 100644 index 000000000000..72eadd1fad2f --- /dev/null +++ b/python/src/com/jetbrains/python/codeInsight/intentions/PyAnnotateTypesIntention.java @@ -0,0 +1,237 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jetbrains.python.codeInsight.intentions; + +import com.google.common.base.Function; +import com.google.common.collect.Lists; +import com.intellij.codeInsight.CodeInsightUtilCore; +import com.intellij.codeInsight.intention.IntentionAction; +import com.intellij.codeInsight.template.*; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.fileEditor.OpenFileDescriptor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.util.IncorrectOperationException; +import com.jetbrains.python.PyBundle; +import com.jetbrains.python.documentation.doctest.PyDocstringFile; +import com.jetbrains.python.psi.*; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +import static com.jetbrains.python.codeInsight.intentions.SpecifyTypeInPy3AnnotationsIntention.*; +import static com.jetbrains.python.codeInsight.intentions.TypeIntention.getCallable; +import static com.jetbrains.python.codeInsight.intentions.TypeIntention.resolvesToFunction; + +/** + * @author traff + */ +public class PyAnnotateTypesIntention implements IntentionAction { + private String myText = PyBundle.message("INTN.annotate.types"); + + @NotNull + public String getText() { + return myText; + } + + @NotNull + public String getFamilyName() { + return PyBundle.message("INTN.annotate.types"); + } + + public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { + if (!(file instanceof PyFile) || file instanceof PyDocstringFile) return false; + + updateText(); + + final PsiElement elementAt = PyUtil.findNonWhitespaceAtOffset(file, editor.getCaretModel().getOffset()); + if (elementAt == null) return false; + + if (resolvesToFunction(elementAt, new Function() { + @Override + public Boolean apply(PyFunction input) { + return true; + } + })) { + updateText(); + return true; + } + return false; + } + + public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + final PsiElement elementAt = PyUtil.findNonWhitespaceAtOffset(file, editor.getCaretModel().getOffset()); + final PyCallable callable = getCallable(elementAt); + + annotateTypes(editor, callable); + } + + public static void annotateTypes(Editor editor, PyCallable callable) { + if (isPy3k(callable.getContainingFile())) { + generatePy3kTypeAnnotations(callable.getProject(), editor, callable); + } + else { + if (callable instanceof PyFunction) { + generateTypeCommentAnnotations(callable.getProject(), (PyFunction)callable); + } + } + } + + private static void generateTypeCommentAnnotations(Project project, PyFunction function) { + + StringBuilder replacementTextBuilder = new StringBuilder("# type: ("); + + PyParameter[] params = function.getParameterList().getParameters(); + + List> templates = Lists.newArrayList(); + + for (int i = 0; i < params.length; i++) { + String type = parameterType(params[i]); + + templates.add(Pair.create(replacementTextBuilder.length(), type)); + + replacementTextBuilder.append(type); + + if (i < params.length - 1) { + replacementTextBuilder.append(", "); + } + } + + replacementTextBuilder.append(") -> "); + + String returnType = returnType(function); + templates.add(Pair.create(replacementTextBuilder.length(), returnType)); + + replacementTextBuilder.append(returnType); + + final PyStatementList statements = function.getStatementList(); + final String indentation = PyIndentUtil.getExpectedElementIndent(statements); + replacementTextBuilder.insert(0, indentation); + replacementTextBuilder.insert(0, "\n"); + + + final PsiDocumentManager manager = PsiDocumentManager.getInstance(project); + final Document document = manager.getDocument(function.getContainingFile()); + + if (document != null) { + final PsiElement beforeStatements = statements.getPrevSibling(); + int offset = beforeStatements.getTextRange().getStartOffset(); + if (":".equals(beforeStatements.getText())) { + offset += 1; + } + try { + document.insertString(offset, replacementTextBuilder.toString()); + } + finally { + manager.commitDocument(document); + } + + + function = CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(function); + + if (function != null) { + final TemplateBuilder builder = + TemplateBuilderFactory.getInstance().createTemplateBuilder(function); + + for (Pair template : templates) { + builder.replaceRange(TextRange.from( + offset - function.getTextRange().getStartOffset() + replacementTextBuilder.toString().indexOf('#') + template.first, + template.second.length()), template.second); + } + + startTemplate(project, function, builder); + } + } + } + + private static void startTemplate(Project project, PyCallable callable, TemplateBuilder builder) { + final Template template = ((TemplateBuilderImpl)builder).buildInlineTemplate(); + ; + + int offset = callable.getTextRange().getStartOffset(); + + final OpenFileDescriptor descriptor = new OpenFileDescriptor( + project, + callable.getContainingFile().getVirtualFile(), + offset + ); + final Editor targetEditor = FileEditorManager.getInstance(project).openTextEditor(descriptor, true); + if (targetEditor != null) { + targetEditor.getCaretModel().moveToOffset(offset); + TemplateManager.getInstance(project).startTemplate(targetEditor, template); + } + } + + private static boolean isPy3k(PsiFile file) { + return LanguageLevel.forElement(file).isPy3K(); + } + + private static void generatePy3kTypeAnnotations(@NotNull Project project, Editor editor, PyCallable callable) { + final TemplateBuilder builder = TemplateBuilderFactory.getInstance().createTemplateBuilder(callable); + + if (callable instanceof PyFunction) { + PyExpression returnType = annotateReturnType(project, (PyFunction) callable, false); + + if (returnType != null) { + builder.replaceElement(returnType, returnType.getText()); + } + } + + if (callable instanceof PyFunction) { + PyFunction function = (PyFunction)callable; + PyParameter[] params = function.getParameterList().getParameters(); + + for (int i = params.length - 1; i >= 0; i--) { + if (params[i] instanceof PyNamedParameter) { + params[i] = annotateParameter(project, editor, (PyNamedParameter)params[i], false); + } + } + + + for (int i = params.length - 1; i >= 0; i--) { + if (params[i] instanceof PyNamedParameter) { + params[i] = CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(params[i]); + PyAnnotation annotation = ((PyNamedParameter)params[i]).getAnnotation(); + if (annotation != null) { + PyExpression annotationValue = annotation.getValue(); + if (annotationValue != null) { + builder.replaceElement(annotationValue, annotationValue.getText()); + } + } + } + } + } + if (callable != null) { + startTemplate(project, callable, builder); + } + } + + @Override + public boolean startInWriteAction() { + return true; + } + + + protected void updateText() { + myText = PyBundle.message("INTN.annotate.types"); + } +} \ No newline at end of file diff --git a/python/src/com/jetbrains/python/codeInsight/intentions/SpecifyTypeInDocstringIntention.java b/python/src/com/jetbrains/python/codeInsight/intentions/SpecifyTypeInDocstringIntention.java index e06bedea1f00..f270def805c3 100644 --- a/python/src/com/jetbrains/python/codeInsight/intentions/SpecifyTypeInDocstringIntention.java +++ b/python/src/com/jetbrains/python/codeInsight/intentions/SpecifyTypeInDocstringIntention.java @@ -25,6 +25,7 @@ import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.IncorrectOperationException; import com.intellij.util.ObjectUtils; import com.jetbrains.python.PyBundle; +import com.jetbrains.python.PyNames; import com.jetbrains.python.debugger.PySignature; import com.jetbrains.python.debugger.PySignatureCacheManager; import com.jetbrains.python.documentation.docstrings.DocStringUtil; @@ -78,7 +79,7 @@ public class SpecifyTypeInDocstringIntention extends TypeIntention { } final PyDocstringGenerator docstringGenerator = PyDocstringGenerator.forDocStringOwner(pyFunction); - String type = "object"; + String type = PyNames.OBJECT; if (param != null) { final String paramName = StringUtil.notNullize(param.getName()); final PySignature signature = PySignatureCacheManager.getInstance(pyFunction.getProject()).findSignature(pyFunction); @@ -88,6 +89,10 @@ public class SpecifyTypeInDocstringIntention extends TypeIntention { docstringGenerator.withParamTypedByName(param, type); } else { + final PySignature signature = PySignatureCacheManager.getInstance(pyFunction.getProject()).findSignature(pyFunction); + if (signature != null) { + type = ObjectUtils.chooseNotNull(signature.getReturnTypeQualifiedName(), type); + } docstringGenerator.withReturnValue(type); } @@ -97,7 +102,7 @@ public class SpecifyTypeInDocstringIntention extends TypeIntention { @Override protected void updateText(boolean isReturn) { - myText = isReturn ? PyBundle.message("INTN.specify.return.type") : PyBundle.message("INTN.specify.type"); + myText = PyBundle.message(isReturn ? "INTN.specify.return.type" : "INTN.specify.type"); } @Override diff --git a/python/src/com/jetbrains/python/codeInsight/intentions/SpecifyTypeInPy3AnnotationsIntention.java b/python/src/com/jetbrains/python/codeInsight/intentions/SpecifyTypeInPy3AnnotationsIntention.java index dd3f1aae4f75..f9b41ab1c7b7 100644 --- a/python/src/com/jetbrains/python/codeInsight/intentions/SpecifyTypeInPy3AnnotationsIntention.java +++ b/python/src/com/jetbrains/python/codeInsight/intentions/SpecifyTypeInPy3AnnotationsIntention.java @@ -28,17 +28,21 @@ import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiReference; +import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.IncorrectOperationException; +import com.intellij.util.ObjectUtils; import com.jetbrains.python.PyBundle; import com.jetbrains.python.PyNames; import com.jetbrains.python.PyTokenTypes; +import com.jetbrains.python.debugger.PySignature; +import com.jetbrains.python.debugger.PySignatureCacheManager; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.impl.PyPsiUtils; import org.jetbrains.annotations.NotNull; /** * User: ktisha - * + *

* Helps to specify type in annotations in python3 */ public class SpecifyTypeInPy3AnnotationsIntention extends TypeIntention { @@ -69,78 +73,120 @@ public class SpecifyTypeInPy3AnnotationsIntention extends TypeIntention { final PyNamedParameter parameter = getParameter(problemElement, resolved); if (parameter != null) { - annotateParameter(project, editor, parameter); + annotateParameter(project, editor, parameter, true); } else { - annotateReturnType(project, editor.getDocument(), elementAt); + PyCallable callable = getCallable(elementAt); + if (callable instanceof PyFunction) { + annotateReturnType(project, (PyFunction)callable, true); + } } } - private static void annotateParameter(Project project, Editor editor, @NotNull PyNamedParameter parameter) { + static PyNamedParameter annotateParameter(Project project, + Editor editor, + @NotNull PyNamedParameter parameter, + boolean createTemplate) { final PyExpression defaultParamValue = parameter.getDefaultValue(); - final String name = StringUtil.notNullize(parameter.getName()); + final String paramName = StringUtil.notNullize(parameter.getName()); final PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project); final String defaultParamText = defaultParamValue == null ? null : defaultParamValue.getText(); - final PyNamedParameter namedParameter = elementGenerator.createParameter(name, defaultParamText, PyNames.OBJECT, + + String paramType = parameterType(parameter); + + + final PyNamedParameter namedParameter = elementGenerator.createParameter(paramName, defaultParamText, paramType, LanguageLevel.forElement(parameter)); assert namedParameter != null; parameter = (PyNamedParameter)parameter.replace(namedParameter); parameter = CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(parameter); editor.getCaretModel().moveToOffset(parameter.getTextOffset()); final PyAnnotation annotation = parameter.getAnnotation(); - if (annotation != null) { + if (annotation != null && createTemplate) { final PyExpression annotationValue = annotation.getValue(); final TemplateBuilder builder = TemplateBuilderFactory.getInstance().createTemplateBuilder(parameter); assert annotationValue != null : "Generated parameter must have annotation"; final int replacementStart = annotation.getStartOffsetInParent() + annotationValue.getStartOffsetInParent(); builder.replaceRange(TextRange.create(replacementStart, - replacementStart + annotationValue.getTextLength()), PyNames.OBJECT); + replacementStart + annotationValue.getTextLength()), paramType); final Template template = ((TemplateBuilderImpl)builder).buildInlineTemplate(); TemplateManager.getInstance(project).startTemplate(editor, template); } + + return parameter; } - private void annotateReturnType(Project project, Document document, PsiElement resolved) { - PyCallable callable = getCallable(resolved); + static String parameterType(PyParameter parameter) { + String paramType = PyNames.OBJECT; - if (callable instanceof PyFunction) { - final String annotationText = " -> " + PyNames.OBJECT; - - final PsiElement prevElem = PyPsiUtils.getPrevNonCommentSibling(((PyFunction)callable).getStatementList(), true); - assert prevElem != null; + PyFunction function = PsiTreeUtil.getParentOfType(parameter, PyFunction.class); + if (function != null) { + final PySignature signature = PySignatureCacheManager.getInstance(parameter.getProject()).findSignature( + function); + String parameterName = parameter.getName(); + if (signature != null && parameterName != null) { + paramType = ObjectUtils.chooseNotNull(signature.getArgTypeQualifiedName(parameterName), paramType); + } + } + return paramType; + } - final PsiDocumentManager manager = PsiDocumentManager.getInstance(project); + + static String returnType(@NotNull PyFunction function) { + String returnType = PyNames.OBJECT; + final PySignature signature = PySignatureCacheManager.getInstance(function.getProject()).findSignature( + function); + if (signature != null) { + returnType = ObjectUtils.chooseNotNull(signature.getReturnTypeQualifiedName(), returnType); + } + return returnType; + } + + public static PyExpression annotateReturnType(Project project, PyFunction function, boolean createTemplate) { + String returnType = returnType(function); + + final String annotationText = " -> " + returnType; + + final PsiElement prevElem = PyPsiUtils.getPrevNonCommentSibling(function.getStatementList(), true); + assert prevElem != null; + + final PsiDocumentManager manager = PsiDocumentManager.getInstance(project); + Document documentWithCallable = manager.getDocument(function.getContainingFile()); + if (documentWithCallable != null) { try { final TextRange range = prevElem.getTextRange(); - manager.doPostponedOperationsAndUnblockDocument(document); + manager.doPostponedOperationsAndUnblockDocument(documentWithCallable); if (prevElem.getNode().getElementType() == PyTokenTypes.COLON) { - document.insertString(range.getStartOffset(), annotationText); + documentWithCallable.insertString(range.getStartOffset(), annotationText); } else { - document.insertString(range.getEndOffset(), annotationText + ":"); + documentWithCallable.insertString(range.getEndOffset(), annotationText + ":"); } } finally { - manager.commitDocument(document); + manager.commitDocument(documentWithCallable); } - - - callable = CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(callable); - final PyAnnotation annotation = ((PyFunction)callable).getAnnotation(); - assert annotation != null; - final PyExpression annotationValue = annotation.getValue(); - assert annotationValue != null : "Generated function must have annotation"; + } + + + function = CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(function); + final PyAnnotation annotation = function.getAnnotation(); + assert annotation != null; + final PyExpression annotationValue = annotation.getValue(); + assert annotationValue != null : "Generated function must have annotation"; + + if (createTemplate) { final int offset = annotationValue.getTextOffset(); final TemplateBuilder builder = TemplateBuilderFactory.getInstance().createTemplateBuilder(annotationValue); - builder.replaceRange(TextRange.create(0, PyNames.OBJECT.length()), PyNames.OBJECT); + builder.replaceRange(TextRange.create(0, returnType.length()), returnType); final Template template = ((TemplateBuilderImpl)builder).buildInlineTemplate(); final OpenFileDescriptor descriptor = new OpenFileDescriptor( project, - callable.getContainingFile().getVirtualFile(), + function.getContainingFile().getVirtualFile(), offset ); final Editor targetEditor = FileEditorManager.getInstance(project).openTextEditor(descriptor, true); @@ -149,6 +195,7 @@ public class SpecifyTypeInPy3AnnotationsIntention extends TypeIntention { TemplateManager.getInstance(project).startTemplate(targetEditor, template); } } + return annotationValue; } @Override diff --git a/python/src/com/jetbrains/python/codeInsight/intentions/TypeIntention.java b/python/src/com/jetbrains/python/codeInsight/intentions/TypeIntention.java index effca1d4e9b9..355b2e0f3267 100644 --- a/python/src/com/jetbrains/python/codeInsight/intentions/TypeIntention.java +++ b/python/src/com/jetbrains/python/codeInsight/intentions/TypeIntention.java @@ -15,6 +15,7 @@ */ package com.jetbrains.python.codeInsight.intentions; +import com.google.common.base.Function; import com.intellij.codeInsight.intention.IntentionAction; import com.intellij.lang.ASTNode; import com.intellij.openapi.editor.Editor; @@ -80,7 +81,7 @@ public abstract class TypeIntention implements IntentionAction { } @Nullable - protected static PyExpression getProblemElement(@Nullable PsiElement elementAt) { + public static PyExpression getProblemElement(@Nullable PsiElement elementAt) { PyExpression problemElement = PsiTreeUtil.getParentOfType(elementAt, PyNamedParameter.class, PyReferenceExpression.class); if (problemElement == null) return null; if (problemElement instanceof PyQualifiedExpression) { @@ -108,13 +109,22 @@ public abstract class TypeIntention implements IntentionAction { } private boolean isAvailableForReturn(@NotNull final PsiElement elementAt) { + return resolvesToFunction(elementAt, new Function() { + @Override + public Boolean apply(PyFunction input) { + return !isReturnTypeDefined(input); + } + }); + } + + static boolean resolvesToFunction(@NotNull PsiElement elementAt, Function isAvailableForFunction) { final PyFunction parentFunction = PsiTreeUtil.getParentOfType(elementAt, PyFunction.class); if (parentFunction != null) { final ASTNode nameNode = parentFunction.getNameNode(); if (nameNode != null) { final PsiElement prev = elementAt.getContainingFile().findElementAt(elementAt.getTextOffset()-1); if (nameNode.getPsi() == elementAt || nameNode.getPsi() == prev) { - return !isReturnTypeDefined(parentFunction); + return isAvailableForFunction.apply(parentFunction); } } } @@ -126,18 +136,19 @@ public abstract class TypeIntention implements IntentionAction { final PsiReference reference = callee.getReference(); if (reference instanceof PsiPolyVariantReference) { final ResolveResult[] results = ((PsiPolyVariantReference)reference).multiResolve(false); - if (results.length == 1) { - final PsiElement result = results[0].getElement(); - if (!(result instanceof PyFunction)) return false; - final PsiFile psiFile = result.getContainingFile(); - if (psiFile == null) return false; - final VirtualFile virtualFile = psiFile.getVirtualFile(); - if (virtualFile != null) { - if (ProjectRootManager.getInstance(psiFile.getProject()).getFileIndex().isInLibraryClasses(virtualFile)) { - return false; + for (int i = 0; i getParameterType(@NotNull final PyNamedParameter param, @NotNull final PyFunction func, @NotNull TypeEvalContext context) { @@ -34,8 +36,25 @@ public class PyCallSignatureTypeProvider extends PyTypeProviderBase { if (typeName != null) { final PyType type = PyTypeParser.getTypeByName(param, typeName); if (type != null) { - final PyType evaluatedType = PyDynamicallyEvaluatedType.create(type); - return Ref.create(evaluatedType); + return Ref.create(PyDynamicallyEvaluatedType.create(type)); + } + } + } + return null; + } + + @Override + public Ref getReturnType(@NotNull final PyCallable callable, @NotNull TypeEvalContext context) { + if (callable instanceof PyFunction) { + PyFunction function = (PyFunction)callable; + PySignature signature = PySignatureCacheManager.getInstance(function.getProject()).findSignature(function); + if (signature != null) { + final String typeName = signature.getReturnType().getTypeQualifiedName(); + if (typeName != null) { + final PyType type = PyTypeParser.getTypeByName(function, typeName); + if (type != null) { + return Ref.create(PyDynamicallyEvaluatedType.create(type)); + } } } } diff --git a/python/src/com/jetbrains/python/debugger/PySignatureCacheManagerImpl.java b/python/src/com/jetbrains/python/debugger/PySignatureCacheManagerImpl.java index 49ab3ad28202..cbb5bef6f6d0 100644 --- a/python/src/com/jetbrains/python/debugger/PySignatureCacheManagerImpl.java +++ b/python/src/com/jetbrains/python/debugger/PySignatureCacheManagerImpl.java @@ -104,7 +104,7 @@ public class PySignatureCacheManagerImpl extends PySignatureCacheManager { else { //noinspection ConstantConditions lines[i] = signatureToString(stringToSignature(file. - getCanonicalPath(), lines[i]).addAllArgs(signature)); + getCanonicalPath(), lines[i]).addAllArgs(signature).addReturnType(signature.getReturnTypeQualifiedName())); } } i++; @@ -140,7 +140,9 @@ public class PySignatureCacheManagerImpl extends PySignatureCacheManager { } private static String signatureToString(PySignature signature) { - return signature.getFunctionName() + "\t" + StringUtil.join(arguments(signature), "\t"); + return signature.getFunctionName() + "\t" + StringUtil.join(arguments(signature), "\t") + + (signature.getReturnType() != null + ? "\t" + StringUtil.join(signature.getReturnType().getTypesList(), "\t") : ""); } private static List arguments(PySignature signature) { @@ -248,8 +250,11 @@ public class PySignatureCacheManagerImpl extends PySignatureCacheManager { if (var.length == 2) { signature = signature.addArgument(var[0], var[1]); } + else if (var.length == 1) { + signature = signature.addReturnType(var[0]); + } else { - throw new IllegalStateException("Should be : format. " + parts[i] + " instead."); + throw new IllegalStateException("Should be : format for arg or for return type; '" + parts[i] + "' instead."); } } return signature; @@ -259,7 +264,7 @@ public class PySignatureCacheManagerImpl extends PySignatureCacheManager { @Nullable private static VirtualFile getFile(@NotNull PySignature signature) { - return LocalFileSystem.getInstance().findFileByPath(signature.getFile()); + return LocalFileSystem.getInstance().refreshAndFindFileByPath(signature.getFile()); } @Nullable @@ -291,7 +296,7 @@ public class PySignatureCacheManagerImpl extends PySignatureCacheManager { } }); } - }, "Cleaning the cache of dynamically collected types", true, myProject); + }, "Cleaning the Cache of Dynamically Collected Types", true, myProject); String message; diff --git a/python/src/com/jetbrains/python/documentation/docstrings/PyDocstringGenerator.java b/python/src/com/jetbrains/python/documentation/docstrings/PyDocstringGenerator.java index 5d0d596fb518..50fea52fba76 100644 --- a/python/src/com/jetbrains/python/documentation/docstrings/PyDocstringGenerator.java +++ b/python/src/com/jetbrains/python/documentation/docstrings/PyDocstringGenerator.java @@ -274,7 +274,8 @@ public class PyDocstringGenerator { String type = paramTypes.get(paramCoordinates); if (type == null && PyCodeInsightSettings.getInstance().INSERT_TYPE_DOCSTUB) { if (signature != null) { - type = StringUtil.notNullize(signature.getArgTypeQualifiedName(param.getName())); + type = StringUtil.notNullize(param.isReturnValue() ? signature.getReturnTypeQualifiedName() : + signature.getArgTypeQualifiedName(param.getName())); } else { type = ""; diff --git a/python/src/com/jetbrains/python/inspections/PyMissingTypeHintsInspection.java b/python/src/com/jetbrains/python/inspections/PyMissingTypeHintsInspection.java new file mode 100644 index 000000000000..081207b1c7f0 --- /dev/null +++ b/python/src/com/jetbrains/python/inspections/PyMissingTypeHintsInspection.java @@ -0,0 +1,101 @@ +/* + * Copyright 2000-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jetbrains.python.inspections; + +import com.intellij.codeInspection.LocalInspectionToolSession; +import com.intellij.codeInspection.LocalQuickFix; +import com.intellij.codeInspection.ProblemDescriptor; +import com.intellij.codeInspection.ProblemsHolder; +import com.intellij.lang.ASTNode; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElementVisitor; +import com.intellij.psi.util.PsiTreeUtil; +import com.jetbrains.python.codeInsight.intentions.PyAnnotateTypesIntention; +import com.jetbrains.python.inspections.quickfix.PyQuickFixUtil; +import com.jetbrains.python.psi.PyElementVisitor; +import com.jetbrains.python.psi.PyFunction; +import com.jetbrains.python.psi.PyNamedParameter; +import com.jetbrains.python.psi.PyParameter; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; + +/** + * @author traff + */ +public class PyMissingTypeHintsInspection extends PyInspection{ + @NotNull + @Override + public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly, @NotNull LocalInspectionToolSession session) { + return new PyElementVisitor() { + @Override + public void visitPyFunction(PyFunction function) { + if (!(function.getTypeComment() != null || typeAnnotationsExist(function))) { + ASTNode nameNode = function.getNameNode(); + if (nameNode != null) { + holder.registerProblem(nameNode.getPsi(), "Type hinting is missing for function definition", new AddTypeHintsQuickFix(function.getName())); + } + } + } + }; + } + + private static boolean typeAnnotationsExist(PyFunction function) { + for (PyParameter param: function.getParameterList().getParameters()) { + PyNamedParameter namedParameter = param.getAsNamed(); + if (namedParameter != null) { + if (namedParameter.getAnnotation() != null) { + return true; + } + } + } + if (function.getAnnotation() != null) { + return true; + } + + return false; + } + + private static class AddTypeHintsQuickFix implements LocalQuickFix { + private String myName; + + public AddTypeHintsQuickFix(@NotNull String name) { + myName = name; + } + + @Nls + @NotNull + @Override + public String getName() { + return "Add type hints for '" + myName + "'"; + } + + @Nls + @NotNull + @Override + public String getFamilyName() { + return "Add type hints"; + } + + @Override + public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + PyFunction function = PsiTreeUtil.getParentOfType(descriptor.getPsiElement(), PyFunction.class); + + if (function != null) { + PyAnnotateTypesIntention.annotateTypes(PyQuickFixUtil.getEditor(function), function); + } + } + } +} diff --git a/python/src/com/jetbrains/python/inspections/PyProtectedMemberInspection.java b/python/src/com/jetbrains/python/inspections/PyProtectedMemberInspection.java index 9b22b33d8b48..b1016ef85ea8 100644 --- a/python/src/com/jetbrains/python/inspections/PyProtectedMemberInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyProtectedMemberInspection.java @@ -93,7 +93,7 @@ public class PyProtectedMemberInspection extends PyInspection { private boolean isImportFromTheSamePackage(PyReferenceExpression importSource) { PsiDirectory directory = importSource.getContainingFile().getContainingDirectory(); - if (PyUtil.isPackage(directory, true, importSource.getContainingFile()) && + if (directory != null && PyUtil.isPackage(directory, true, importSource.getContainingFile()) && directory.getName().equals(importSource.getName())) { return true; } diff --git a/python/src/com/jetbrains/python/inspections/quickfix/DocstringQuickFix.java b/python/src/com/jetbrains/python/inspections/quickfix/DocstringQuickFix.java index 0622727ebc1b..a42ddfdc7cca 100644 --- a/python/src/com/jetbrains/python/inspections/quickfix/DocstringQuickFix.java +++ b/python/src/com/jetbrains/python/inspections/quickfix/DocstringQuickFix.java @@ -17,12 +17,7 @@ package com.jetbrains.python.inspections.quickfix; import com.intellij.codeInspection.LocalQuickFix; import com.intellij.codeInspection.ProblemDescriptor; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.EditorFactory; import com.intellij.openapi.project.Project; -import com.intellij.psi.PsiDocumentManager; -import com.intellij.psi.PsiElement; import com.intellij.psi.SmartPointerManager; import com.intellij.psi.SmartPsiElementPointer; import com.intellij.psi.util.PsiTreeUtil; @@ -73,20 +68,6 @@ public class DocstringQuickFix implements LocalQuickFix { return "Fix docstring"; } - @Nullable - private static Editor getEditor(@NotNull PsiElement element) { - Document document = PsiDocumentManager.getInstance(element.getProject()).getDocument(element.getContainingFile()); - if (document != null) { - final EditorFactory instance = EditorFactory.getInstance(); - if (instance == null) return null; - Editor[] editors = instance.getEditors(document); - if (editors.length > 0) { - return editors[0]; - } - } - return null; - } - public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { PyDocStringOwner docStringOwner = PsiTreeUtil.getParentOfType(descriptor.getPsiElement(), PyDocStringOwner.class); if (docStringOwner == null) return; @@ -113,7 +94,7 @@ public class DocstringQuickFix implements LocalQuickFix { private static void addEmptyDocstring(@NotNull PyDocStringOwner docStringOwner) { if (docStringOwner instanceof PyFunction || docStringOwner instanceof PyClass && ((PyClass)docStringOwner).findInitOrNew(false, null) != null) { - PyGenerateDocstringIntention.generateDocstring(docStringOwner, getEditor(docStringOwner)); + PyGenerateDocstringIntention.generateDocstring(docStringOwner, PyQuickFixUtil.getEditor(docStringOwner)); } } } diff --git a/python/src/com/jetbrains/python/inspections/quickfix/PyImplementMethodsQuickFix.java b/python/src/com/jetbrains/python/inspections/quickfix/PyImplementMethodsQuickFix.java index 282bca97dbb5..d480f4e203e3 100644 --- a/python/src/com/jetbrains/python/inspections/quickfix/PyImplementMethodsQuickFix.java +++ b/python/src/com/jetbrains/python/inspections/quickfix/PyImplementMethodsQuickFix.java @@ -17,11 +17,8 @@ package com.jetbrains.python.inspections.quickfix; import com.intellij.codeInspection.LocalQuickFixOnPsiElement; import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.EditorFactory; import com.intellij.openapi.project.Project; -import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.jetbrains.python.PyBundle; @@ -31,7 +28,6 @@ import com.jetbrains.python.psi.PyClass; import com.jetbrains.python.psi.PyFunction; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.Set; @@ -62,7 +58,7 @@ public class PyImplementMethodsQuickFix extends LocalQuickFixOnPsiElement { @Override public void invoke(@NotNull Project project, @NotNull PsiFile file, @NotNull PsiElement startElement, @NotNull PsiElement endElement) { - final Editor editor = getEditor(project, file); + final Editor editor = PyQuickFixUtil.getEditor(file); if (editor != null && startElement instanceof PyClass) { if (ApplicationManager.getApplication().isUnitTestMode()) { @@ -81,18 +77,4 @@ public class PyImplementMethodsQuickFix extends LocalQuickFixOnPsiElement { } } } - - @Nullable - private static Editor getEditor(Project project, PsiFile file) { - Document document = PsiDocumentManager.getInstance(project).getDocument(file); - if (document != null) { - final EditorFactory instance = EditorFactory.getInstance(); - if (instance == null) return null; - Editor[] editors = instance.getEditors(document); - if (editors.length > 0) { - return editors[0]; - } - } - return null; - } } diff --git a/python/src/com/jetbrains/python/inspections/quickfix/PyQuickFixUtil.java b/python/src/com/jetbrains/python/inspections/quickfix/PyQuickFixUtil.java new file mode 100644 index 000000000000..3ad435445642 --- /dev/null +++ b/python/src/com/jetbrains/python/inspections/quickfix/PyQuickFixUtil.java @@ -0,0 +1,43 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jetbrains.python.inspections.quickfix; + +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.EditorFactory; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiElement; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author traff + */ +public class PyQuickFixUtil { + @Nullable + public static Editor getEditor(@NotNull PsiElement element) { + Document document = PsiDocumentManager.getInstance(element.getProject()).getDocument(element.getContainingFile()); + if (document != null) { + final EditorFactory instance = EditorFactory.getInstance(); + if (instance == null) return null; + Editor[] editors = instance.getEditors(document); + if (editors.length > 0) { + return editors[0]; + } + } + return null; + } +} diff --git a/python/src/com/jetbrains/python/psi/types/PyDynamicallyEvaluatedType.java b/python/src/com/jetbrains/python/psi/types/PyDynamicallyEvaluatedType.java index 4dff5a00d886..5e961060c1a1 100644 --- a/python/src/com/jetbrains/python/psi/types/PyDynamicallyEvaluatedType.java +++ b/python/src/com/jetbrains/python/psi/types/PyDynamicallyEvaluatedType.java @@ -15,7 +15,6 @@ */ package com.jetbrains.python.psi.types; -import com.intellij.openapi.project.Project; import com.jetbrains.python.PyNames; import org.jetbrains.annotations.NotNull; diff --git a/python/testData/dynamicTypes/1.py b/python/testData/dynamicTypes/1.py new file mode 100644 index 000000000000..e7c846b2c640 --- /dev/null +++ b/python/testData/dynamicTypes/1.py @@ -0,0 +1,4 @@ +def foo(x): + return x + +foo(1) \ No newline at end of file diff --git a/python/testData/dynamicTypes/1_after.py b/python/testData/dynamicTypes/1_after.py new file mode 100644 index 000000000000..e647aea92013 --- /dev/null +++ b/python/testData/dynamicTypes/1_after.py @@ -0,0 +1,11 @@ +def foo(x): + """ + + :param x: + :type x: int + :return: + :rtype: int + """ + return x + +foo(1) \ No newline at end of file diff --git a/python/testData/inspections/PyMissingTypeHintsInspection/noAnnotations.py b/python/testData/inspections/PyMissingTypeHintsInspection/noAnnotations.py new file mode 100644 index 000000000000..73bdd73dbc82 --- /dev/null +++ b/python/testData/inspections/PyMissingTypeHintsInspection/noAnnotations.py @@ -0,0 +1,2 @@ +def foo(x, y): + pass \ No newline at end of file diff --git a/python/testData/inspections/PyMissingTypeHintsInspection/py3kAnnotations.py b/python/testData/inspections/PyMissingTypeHintsInspection/py3kAnnotations.py new file mode 100644 index 000000000000..caefec0ac1f7 --- /dev/null +++ b/python/testData/inspections/PyMissingTypeHintsInspection/py3kAnnotations.py @@ -0,0 +1,2 @@ +def foo(x: int, y: int) -> str: + pass \ No newline at end of file diff --git a/python/testData/inspections/PyMissingTypeHintsInspection/typeComment.py b/python/testData/inspections/PyMissingTypeHintsInspection/typeComment.py new file mode 100644 index 000000000000..38efd7e111c2 --- /dev/null +++ b/python/testData/inspections/PyMissingTypeHintsInspection/typeComment.py @@ -0,0 +1,3 @@ +def foo(x): + # type: (int) -> int + pass \ No newline at end of file diff --git a/python/testData/inspections/PyMissingTypeHintsInspection/typeCommentOnTheSameLine.py b/python/testData/inspections/PyMissingTypeHintsInspection/typeCommentOnTheSameLine.py new file mode 100644 index 000000000000..2b42a1884977 --- /dev/null +++ b/python/testData/inspections/PyMissingTypeHintsInspection/typeCommentOnTheSameLine.py @@ -0,0 +1,2 @@ +def foo(x): # type: (int) -> int + pass \ No newline at end of file diff --git a/python/testData/intentions/PyAnnotateTypesIntentionTest/caretOnDefinition.py b/python/testData/intentions/PyAnnotateTypesIntentionTest/caretOnDefinition.py new file mode 100644 index 000000000000..cb92a3843add --- /dev/null +++ b/python/testData/intentions/PyAnnotateTypesIntentionTest/caretOnDefinition.py @@ -0,0 +1,2 @@ +def foo(x, y): + pass \ No newline at end of file diff --git a/python/testData/intentions/PyAnnotateTypesIntentionTest/caretOnDefinition_after.py b/python/testData/intentions/PyAnnotateTypesIntentionTest/caretOnDefinition_after.py new file mode 100644 index 000000000000..a313e5f14fb9 --- /dev/null +++ b/python/testData/intentions/PyAnnotateTypesIntentionTest/caretOnDefinition_after.py @@ -0,0 +1,2 @@ +def foo(x: object, y: object) -> object: + pass \ No newline at end of file diff --git a/python/testData/intentions/PyAnnotateTypesIntentionTest/caretOnImportedInvocation.py b/python/testData/intentions/PyAnnotateTypesIntentionTest/caretOnImportedInvocation.py new file mode 100644 index 000000000000..39e5e0eeeb14 --- /dev/null +++ b/python/testData/intentions/PyAnnotateTypesIntentionTest/caretOnImportedInvocation.py @@ -0,0 +1,3 @@ +from foo_decl import foo + +foo(1, 1) \ No newline at end of file diff --git a/python/testData/intentions/PyAnnotateTypesIntentionTest/caretOnImportedInvocation_after.py b/python/testData/intentions/PyAnnotateTypesIntentionTest/caretOnImportedInvocation_after.py new file mode 100644 index 000000000000..26ce2df47d40 --- /dev/null +++ b/python/testData/intentions/PyAnnotateTypesIntentionTest/caretOnImportedInvocation_after.py @@ -0,0 +1,3 @@ +from foo_decl import foo + +foo(1, 1) \ No newline at end of file diff --git a/python/testData/intentions/PyAnnotateTypesIntentionTest/caretOnInvocation.py b/python/testData/intentions/PyAnnotateTypesIntentionTest/caretOnInvocation.py new file mode 100644 index 000000000000..6c5aa528d332 --- /dev/null +++ b/python/testData/intentions/PyAnnotateTypesIntentionTest/caretOnInvocation.py @@ -0,0 +1,5 @@ +def foo(x, y): + pass + + +foo(1, 1) \ No newline at end of file diff --git a/python/testData/intentions/PyAnnotateTypesIntentionTest/caretOnInvocation_after.py b/python/testData/intentions/PyAnnotateTypesIntentionTest/caretOnInvocation_after.py new file mode 100644 index 000000000000..38197b897a1d --- /dev/null +++ b/python/testData/intentions/PyAnnotateTypesIntentionTest/caretOnInvocation_after.py @@ -0,0 +1,5 @@ +def foo(x: object, y: object) -> object: + pass + + +foo(1, 1) \ No newline at end of file diff --git a/python/testData/intentions/PyAnnotateTypesIntentionTest/foo_decl.py b/python/testData/intentions/PyAnnotateTypesIntentionTest/foo_decl.py new file mode 100644 index 000000000000..282d108e2e52 --- /dev/null +++ b/python/testData/intentions/PyAnnotateTypesIntentionTest/foo_decl.py @@ -0,0 +1,2 @@ +def foo(x, y): + pass \ No newline at end of file diff --git a/python/testData/intentions/PyAnnotateTypesIntentionTest/foo_decl_after.py b/python/testData/intentions/PyAnnotateTypesIntentionTest/foo_decl_after.py new file mode 100644 index 000000000000..a313e5f14fb9 --- /dev/null +++ b/python/testData/intentions/PyAnnotateTypesIntentionTest/foo_decl_after.py @@ -0,0 +1,2 @@ +def foo(x: object, y: object) -> object: + pass \ No newline at end of file diff --git a/python/testData/intentions/PyAnnotateTypesIntentionTest/typeComment.py b/python/testData/intentions/PyAnnotateTypesIntentionTest/typeComment.py new file mode 100644 index 000000000000..cb92a3843add --- /dev/null +++ b/python/testData/intentions/PyAnnotateTypesIntentionTest/typeComment.py @@ -0,0 +1,2 @@ +def foo(x, y): + pass \ No newline at end of file diff --git a/python/testData/intentions/PyAnnotateTypesIntentionTest/typeComment_after.py b/python/testData/intentions/PyAnnotateTypesIntentionTest/typeComment_after.py new file mode 100644 index 000000000000..cb635649431a --- /dev/null +++ b/python/testData/intentions/PyAnnotateTypesIntentionTest/typeComment_after.py @@ -0,0 +1,3 @@ +def foo(x, y): + # type: (object, object) -> object + pass \ No newline at end of file diff --git a/python/testData/intentions/SpecifyTypeInPy3AnnotationsIntentionTest/caretOnDefinition.py b/python/testData/intentions/SpecifyTypeInPy3AnnotationsIntentionTest/caretOnDefinition.py new file mode 100644 index 000000000000..cb92a3843add --- /dev/null +++ b/python/testData/intentions/SpecifyTypeInPy3AnnotationsIntentionTest/caretOnDefinition.py @@ -0,0 +1,2 @@ +def foo(x, y): + pass \ No newline at end of file diff --git a/python/testData/intentions/SpecifyTypeInPy3AnnotationsIntentionTest/caretOnDefinition_after.py b/python/testData/intentions/SpecifyTypeInPy3AnnotationsIntentionTest/caretOnDefinition_after.py new file mode 100644 index 000000000000..50cfaf949af5 --- /dev/null +++ b/python/testData/intentions/SpecifyTypeInPy3AnnotationsIntentionTest/caretOnDefinition_after.py @@ -0,0 +1,2 @@ +def foo(x, y) -> object: + pass \ No newline at end of file diff --git a/python/testData/intentions/SpecifyTypeInPy3AnnotationsIntentionTest/caretOnImportedInvocation.py b/python/testData/intentions/SpecifyTypeInPy3AnnotationsIntentionTest/caretOnImportedInvocation.py new file mode 100644 index 000000000000..39e5e0eeeb14 --- /dev/null +++ b/python/testData/intentions/SpecifyTypeInPy3AnnotationsIntentionTest/caretOnImportedInvocation.py @@ -0,0 +1,3 @@ +from foo_decl import foo + +foo(1, 1) \ No newline at end of file diff --git a/python/testData/intentions/SpecifyTypeInPy3AnnotationsIntentionTest/caretOnImportedInvocation_after.py b/python/testData/intentions/SpecifyTypeInPy3AnnotationsIntentionTest/caretOnImportedInvocation_after.py new file mode 100644 index 000000000000..26ce2df47d40 --- /dev/null +++ b/python/testData/intentions/SpecifyTypeInPy3AnnotationsIntentionTest/caretOnImportedInvocation_after.py @@ -0,0 +1,3 @@ +from foo_decl import foo + +foo(1, 1) \ No newline at end of file diff --git a/python/testData/intentions/SpecifyTypeInPy3AnnotationsIntentionTest/caretOnInvocation.py b/python/testData/intentions/SpecifyTypeInPy3AnnotationsIntentionTest/caretOnInvocation.py new file mode 100644 index 000000000000..6c5aa528d332 --- /dev/null +++ b/python/testData/intentions/SpecifyTypeInPy3AnnotationsIntentionTest/caretOnInvocation.py @@ -0,0 +1,5 @@ +def foo(x, y): + pass + + +foo(1, 1) \ No newline at end of file diff --git a/python/testData/intentions/SpecifyTypeInPy3AnnotationsIntentionTest/caretOnInvocation_after.py b/python/testData/intentions/SpecifyTypeInPy3AnnotationsIntentionTest/caretOnInvocation_after.py new file mode 100644 index 000000000000..5b42ec7130fc --- /dev/null +++ b/python/testData/intentions/SpecifyTypeInPy3AnnotationsIntentionTest/caretOnInvocation_after.py @@ -0,0 +1,5 @@ +def foo(x, y) -> object: + pass + + +foo(1, 1) \ No newline at end of file diff --git a/python/testData/intentions/SpecifyTypeInPy3AnnotationsIntentionTest/caretOnParamUsage.py b/python/testData/intentions/SpecifyTypeInPy3AnnotationsIntentionTest/caretOnParamUsage.py new file mode 100644 index 000000000000..16d57c86b703 --- /dev/null +++ b/python/testData/intentions/SpecifyTypeInPy3AnnotationsIntentionTest/caretOnParamUsage.py @@ -0,0 +1,3 @@ +def foo(var): + print(var) + pass \ No newline at end of file diff --git a/python/testData/intentions/SpecifyTypeInPy3AnnotationsIntentionTest/caretOnParamUsage_after.py b/python/testData/intentions/SpecifyTypeInPy3AnnotationsIntentionTest/caretOnParamUsage_after.py new file mode 100644 index 000000000000..421e1015965a --- /dev/null +++ b/python/testData/intentions/SpecifyTypeInPy3AnnotationsIntentionTest/caretOnParamUsage_after.py @@ -0,0 +1,3 @@ +def foo(var: object): + print(var) + pass \ No newline at end of file diff --git a/python/testData/intentions/SpecifyTypeInPy3AnnotationsIntentionTest/foo_decl.py b/python/testData/intentions/SpecifyTypeInPy3AnnotationsIntentionTest/foo_decl.py new file mode 100644 index 000000000000..282d108e2e52 --- /dev/null +++ b/python/testData/intentions/SpecifyTypeInPy3AnnotationsIntentionTest/foo_decl.py @@ -0,0 +1,2 @@ +def foo(x, y): + pass \ No newline at end of file diff --git a/python/testData/intentions/SpecifyTypeInPy3AnnotationsIntentionTest/foo_decl_after.py b/python/testData/intentions/SpecifyTypeInPy3AnnotationsIntentionTest/foo_decl_after.py new file mode 100644 index 000000000000..50cfaf949af5 --- /dev/null +++ b/python/testData/intentions/SpecifyTypeInPy3AnnotationsIntentionTest/foo_decl_after.py @@ -0,0 +1,2 @@ +def foo(x, y) -> object: + pass \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/env/PyEnvTaskRunner.java b/python/testSrc/com/jetbrains/env/PyEnvTaskRunner.java index 8c7ffec9a571..81239701f2d5 100644 --- a/python/testSrc/com/jetbrains/env/PyEnvTaskRunner.java +++ b/python/testSrc/com/jetbrains/env/PyEnvTaskRunner.java @@ -107,7 +107,7 @@ public class PyEnvTaskRunner { if (url == null) { return null; } - return PyTestSdkTools.createTempSdk(url, SdkCreationType.SDK_PACKAGES_AND_SKELETONS, null); + return PyTestSdkTools.createTempSdk(url, SdkCreationType.EMPTY_SDK, null); } protected boolean shouldRun(String root, PyTestTask task) { diff --git a/python/testSrc/com/jetbrains/env/PyTestTask.java b/python/testSrc/com/jetbrains/env/PyTestTask.java index 64c76c9b888f..6175290db617 100644 --- a/python/testSrc/com/jetbrains/env/PyTestTask.java +++ b/python/testSrc/com/jetbrains/env/PyTestTask.java @@ -50,6 +50,9 @@ public abstract class PyTestTask { public void after() throws Exception { } + public void doFinally() { + } + public void useNormalTimeout() { } diff --git a/python/testSrc/com/jetbrains/env/python/PyDynamicTypesTest.java b/python/testSrc/com/jetbrains/env/python/PyDynamicTypesTest.java new file mode 100644 index 000000000000..d39f45ad0571 --- /dev/null +++ b/python/testSrc/com/jetbrains/env/python/PyDynamicTypesTest.java @@ -0,0 +1,99 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jetbrains.env.python; + +import com.google.common.collect.Lists; +import com.intellij.codeInsight.intention.IntentionAction; +import com.intellij.openapi.editor.LogicalPosition; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.testFramework.EditorTestUtil; +import com.jetbrains.env.PyEnvTestCase; +import com.jetbrains.env.python.debug.PyDebuggerTask; +import com.jetbrains.python.PyBundle; +import com.jetbrains.python.debugger.PyDebuggerOptionsProvider; +import com.jetbrains.python.debugger.PySignatureCacheManagerImpl; + +import java.io.IOException; + +/** + * @author traff + */ + +public class PyDynamicTypesTest extends PyEnvTestCase { + public void test1() throws Exception { + doTest(getTestName(true) + ".py"); + } + + private void doTest(final String scriptName) { + runPythonTest(new PyDebuggerTask("/" + "dynamicTypes", scriptName) { + @Override + public void setUp(String testName) throws Exception { + super.setUp(testName); + } + + @Override + public void before() throws Exception { + PyDebuggerOptionsProvider.getInstance(myFixture.getProject()).setSaveCallSignatures(true); + } + + public void doFinally() { + try { + PySignatureCacheManagerImpl.CALL_SIGNATURES_ATTRIBUTE.writeAttributeBytes(getVirtualFile(), "".getBytes()); + } + catch (IOException e) { + //pass + } + PyDebuggerOptionsProvider.getInstance(myFixture.getProject()).setSaveCallSignatures(false); + } + + private VirtualFile getVirtualFile() { + return LocalFileSystem.getInstance() + .refreshAndFindFileByPath( + getTestDataPath() + + "/dynamicTypes/" + + scriptName); + } + + @Override + public void testing() throws Exception { + waitForTerminate(); + + edt(() -> { + myFixture.configureByFile("dynamicTypes/" + scriptName); + + try { + //copy signature attributes from real file to temporary test file + byte[] bytes = PySignatureCacheManagerImpl.CALL_SIGNATURES_ATTRIBUTE + .readAttributeBytes(getVirtualFile()); + PySignatureCacheManagerImpl.CALL_SIGNATURES_ATTRIBUTE.writeAttributeBytes(myFixture.getFile().getVirtualFile(), + bytes); + } + catch (IOException e) { + throw new RuntimeException(e); + } + + EditorTestUtil.setCaretsAndSelection(myFixture.getEditor(), new EditorTestUtil.CaretAndSelectionState( + Lists.newArrayList(new EditorTestUtil.CaretInfo(new LogicalPosition(0, 6), null)), null)); + final IntentionAction action = myFixture.findSingleIntention(PyBundle.message("INTN.doc.string.stub")); + myFixture.launchAction(action); + myFixture.checkResultByFile("dynamicTypes/" + getTestName(true) + "_after.py"); + }); + } + }); + } +} + diff --git a/python/testSrc/com/jetbrains/env/python/PythonDebuggerTest.java b/python/testSrc/com/jetbrains/env/python/PythonDebuggerTest.java index d0a715a9d729..d7fb13129d0b 100644 --- a/python/testSrc/com/jetbrains/env/python/PythonDebuggerTest.java +++ b/python/testSrc/com/jetbrains/env/python/PythonDebuggerTest.java @@ -441,7 +441,7 @@ public class PythonDebuggerTest extends PyEnvTestCase { }); } - private static void createExceptionBreak(IdeaProjectTestFixture fixture, + public static void createExceptionBreak(IdeaProjectTestFixture fixture, boolean notifyOnTerminate, boolean notifyOnFirst, boolean ignoreLibraries) { diff --git a/python/testSrc/com/jetbrains/env/python/debug/PyBaseDebuggerTask.java b/python/testSrc/com/jetbrains/env/python/debug/PyBaseDebuggerTask.java index 881e366306f1..6c6f05240de3 100644 --- a/python/testSrc/com/jetbrains/env/python/debug/PyBaseDebuggerTask.java +++ b/python/testSrc/com/jetbrains/env/python/debug/PyBaseDebuggerTask.java @@ -316,6 +316,8 @@ public abstract class PyBaseDebuggerTask extends PyExecutionFixtureTestTask { throw new RuntimeException(output(), e); } finally { + doFinally(); + clearAllBreakpoints(); setProcessCanTerminate(true); diff --git a/python/testSrc/com/jetbrains/env/python/debug/PyDebuggerTask.java b/python/testSrc/com/jetbrains/env/python/debug/PyDebuggerTask.java index 7f61cded1351..b6069929e225 100644 --- a/python/testSrc/com/jetbrains/env/python/debug/PyDebuggerTask.java +++ b/python/testSrc/com/jetbrains/env/python/debug/PyDebuggerTask.java @@ -29,6 +29,7 @@ import com.intellij.openapi.application.WriteAction; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Key; import com.intellij.xdebugger.*; +import com.jetbrains.env.python.PythonDebuggerTest; import com.jetbrains.python.debugger.PyDebugProcess; import com.jetbrains.python.debugger.PyDebugRunner; import com.jetbrains.python.run.PythonCommandLineState; @@ -117,6 +118,8 @@ public class PyDebuggerTask extends PyBaseDebuggerTask { final int serverLocalPort = serverSocket.getLocalPort(); final RunProfile profile = env.getRunProfile(); + PythonDebuggerTest.createExceptionBreak(myFixture, false, false, false); //turn off exception breakpoints by default + before(); setProcessCanTerminate(false); diff --git a/python/testSrc/com/jetbrains/python/inspections/PyMissingTypeHintsInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyMissingTypeHintsInspectionTest.java new file mode 100644 index 000000000000..fd6ca83100bc --- /dev/null +++ b/python/testSrc/com/jetbrains/python/inspections/PyMissingTypeHintsInspectionTest.java @@ -0,0 +1,53 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jetbrains.python.inspections; + +import com.jetbrains.python.fixtures.PyTestCase; +import com.jetbrains.python.psi.LanguageLevel; +import com.jetbrains.python.psi.impl.PythonLanguageLevelPusher; + +/** + * @author traff + */ +public class PyMissingTypeHintsInspectionTest extends PyTestCase { + public void testPy3kAnnotations() { + doTest(LanguageLevel.PYTHON35); + } + + public void testNoAnnotations() { + doTest(LanguageLevel.PYTHON35); + } + + public void testTypeComment() { + doTest(LanguageLevel.PYTHON27); + } + + public void testTypeCommentOnTheSameLine() { + doTest(LanguageLevel.PYTHON27); + } + + private void doTest(LanguageLevel languageLevel) { + PythonLanguageLevelPusher.setForcedLanguageLevel(myFixture.getProject(), languageLevel); + try { + myFixture.configureByFile("inspections/PyMissingTypeHintsInspection/" + getTestName(true) + ".py"); + myFixture.enableInspections(PyMissingTypeHintsInspection.class); + myFixture.checkHighlighting(false, false, true); + } + finally { + PythonLanguageLevelPusher.setForcedLanguageLevel(myFixture.getProject(), null); + } + } +} diff --git a/python/testSrc/com/jetbrains/python/intentions/PyAnnotateTypesIntentionTest.java b/python/testSrc/com/jetbrains/python/intentions/PyAnnotateTypesIntentionTest.java new file mode 100644 index 000000000000..77cd98c1da43 --- /dev/null +++ b/python/testSrc/com/jetbrains/python/intentions/PyAnnotateTypesIntentionTest.java @@ -0,0 +1,53 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jetbrains.python.intentions; + +import com.jetbrains.python.PyBundle; +import com.jetbrains.python.psi.LanguageLevel; +import com.jetbrains.python.psi.impl.PythonLanguageLevelPusher; + +/** + * @author traff + */ +public class PyAnnotateTypesIntentionTest extends PyIntentionTestCase { + public void testCaretOnDefinition() { + doTest(); + } + + public void testCaretOnInvocation() { + doTest(); + } + + public void testCaretOnImportedInvocation() { + PythonLanguageLevelPusher.setForcedLanguageLevel(myFixture.getProject(), LanguageLevel.PYTHON30); + try { + doIntentionTest(PyBundle.message("INTN.annotate.types"), getTestName(true) + ".py", "foo_decl.py"); + myFixture.checkResultByFile("foo_decl.py", "foo_decl_after.py", false); + } + finally { + PythonLanguageLevelPusher.setForcedLanguageLevel(myFixture.getProject(), null); + } + } + + public void testTypeComment() { + doTest(PyBundle.message("INTN.annotate.types"), LanguageLevel.PYTHON27); + } + + + private void doTest() { + doTest(PyBundle.message("INTN.annotate.types"), LanguageLevel.PYTHON30); + } +} diff --git a/python/testSrc/com/jetbrains/python/intentions/PyIntentionTestCase.java b/python/testSrc/com/jetbrains/python/intentions/PyIntentionTestCase.java index 0d2bee5822c6..cd23d8b7c9be 100644 --- a/python/testSrc/com/jetbrains/python/intentions/PyIntentionTestCase.java +++ b/python/testSrc/com/jetbrains/python/intentions/PyIntentionTestCase.java @@ -42,9 +42,13 @@ public abstract class PyIntentionTestCase extends PyTestCase { } } - protected void doIntentionTest(final String hint) { + protected void doIntentionTest(final String hint, String ... files) { final String testFileName = getTestName(true); - myFixture.configureByFile(testFileName + ".py"); + if (files.length>0) { + myFixture.configureByFiles(files); + } else { + myFixture.configureByFile(testFileName + ".py"); + } final IntentionAction intentionAction = myFixture.findSingleIntention(hint); assertNotNull(intentionAction); myFixture.launchAction(intentionAction); diff --git a/python/testSrc/com/jetbrains/python/intentions/SpecifyTypeInPy3AnnotationsIntentionTest.java b/python/testSrc/com/jetbrains/python/intentions/SpecifyTypeInPy3AnnotationsIntentionTest.java new file mode 100644 index 000000000000..01cc3d2f9c6e --- /dev/null +++ b/python/testSrc/com/jetbrains/python/intentions/SpecifyTypeInPy3AnnotationsIntentionTest.java @@ -0,0 +1,60 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jetbrains.python.intentions; + +import com.jetbrains.python.PyBundle; +import com.jetbrains.python.psi.LanguageLevel; +import com.jetbrains.python.psi.impl.PythonLanguageLevelPusher; + +/** + * @author traff + */ +public class SpecifyTypeInPy3AnnotationsIntentionTest extends PyIntentionTestCase { + public void testCaretOnDefinition() { + doTestReturnType(); + } + + + + public void testCaretOnInvocation() { + doTestReturnType(); + } + + public void testCaretOnImportedInvocation() { + PythonLanguageLevelPusher.setForcedLanguageLevel(myFixture.getProject(), LanguageLevel.PYTHON30); + try { + doIntentionTest(PyBundle.message("INTN.specify.return.type.in.annotation"), getTestName(true) + ".py", "foo_decl.py"); + myFixture.checkResultByFile("foo_decl.py", "foo_decl_after.py", false); + } + finally { + PythonLanguageLevelPusher.setForcedLanguageLevel(myFixture.getProject(), null); + } + } + + public void testCaretOnParamUsage() { + doTestParam(); + } + + + private void doTestReturnType() { + doTest(PyBundle.message("INTN.specify.return.type.in.annotation"), LanguageLevel.PYTHON30); + } + + + private void doTestParam() { + doTest(PyBundle.message("INTN.specify.type.in.annotation"), LanguageLevel.PYTHON30); + } +} diff --git a/python/testSrc/com/jetbrains/python/sdkTools/PyTestSdkTools.java b/python/testSrc/com/jetbrains/python/sdkTools/PyTestSdkTools.java index 3a010450382d..ff3d241454c1 100644 --- a/python/testSrc/com/jetbrains/python/sdkTools/PyTestSdkTools.java +++ b/python/testSrc/com/jetbrains/python/sdkTools/PyTestSdkTools.java @@ -62,7 +62,11 @@ public final class PyTestSdkTools { @Override public void run() { - final Sdk sdk = SdkConfigurationUtil.setupSdk(NO_SDK, sdkHome, PythonSdkType.getInstance(), true, null, null); + final Sdk sdk = SdkConfigurationUtil.setupSdk(NO_SDK, sdkHome, new PythonSdkType() { + @Override + public void setupSdkPaths(@NotNull Sdk sdk) { + } + }, true, null, null); Assert.assertNotNull("Failed to create SDK on " + sdkHome, sdk); ref.set(sdk); }