Moved ipython notebook to pycharm community

This commit is contained in:
Ekaterina Tuzova
2014-10-03 19:58:42 +04:00
77 changed files with 5418 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
intellij-ipnb
=============
IPython notebook support in IntelliJ
+24
View File
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PLUGIN_MODULE" version="4">
<component name="DevKit.ModuleBuildProperties" url="file://$MODULE_DIR$/resources/META-INF/plugin.xml" />
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/resources" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/testSrc" isTestSource="true" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module-library">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/lib/java_websocket.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
</component>
</module>
+35
View File
@@ -0,0 +1,35 @@
There are 4 possible cell types:
- Markdown
- Heading
- Raw
- Code
Markdown cell type consists of:
- metadata
- Source <- List of Strings
Heading cell type consists of:
- metadata
- Source <- List of Strings
- level <- int
Raw cell type consists of:
- metadata
Code cell type consists of:
- metadata
- collapsed <- Boolean
- input <- List of Strings
- language <- String
- prompt_number <- int
- outputs <- List of Outputs
Output of Code cell consists of:
- metadata
- output_type <- String
- text <- String
- one of [png, latex, stream, ..] <- String
- prompt_number <- int *present only with latex field
Cells are packed into worksheets.
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
pyzmq
+11
View File
@@ -0,0 +1,11 @@
"""Tests of interactions with IPython kernel via 0MQ."""
from unittest import TestCase
import zmq
class ZMQVersionTest(TestCase):
def test_version_available(self):
self.assertIsNotNone(zmq.pyzmq_version())
+43
View File
@@ -0,0 +1,43 @@
<idea-plugin version="2" xmlns:xi="http://www.w3.org/2001/XInclude">
<name>IPython Notebook</name>
<id>org.jetbrains.plugins.ipnb</id>
<version>0.1</version>
<vendor>JetBrains</vendor>
<description>IPython Notebook support</description>
<depends>com.intellij.modules.platform</depends>
<depends optional="true">Pythonid</depends>
<depends optional="true">com.intellij.modules.python</depends>
<project-components>
<component>
<implementation-class>org.jetbrains.plugins.ipnb.configuration.IpnbConnectionManager</implementation-class>
</component>
</project-components>
<extensions defaultExtensionNs="com.intellij">
<fileEditorProvider implementation="org.jetbrains.plugins.ipnb.editor.IpnbEditorProvider"/>
<fileTypeFactory implementation="org.jetbrains.plugins.ipnb.IpnbFileTypeFactory"/>
<errorHandler implementation="com.intellij.diagnostic.ITNReporter"/>
<projectConfigurable groupId="tools" instance="org.jetbrains.plugins.ipnb.configuration.IpnbConfigurable"
id="org.jetbrains.plugins.ipnb.configuration.IpnbConfigurable" displayName="IPython Notebook"
nonDefaultProject="true"/>
<projectService serviceInterface="org.jetbrains.plugins.ipnb.configuration.IpnbSettings"
serviceImplementation="org.jetbrains.plugins.ipnb.configuration.IpnbSettings"/>
<lang.parserDefinition language="IpnbPython" implementationClass="org.jetbrains.plugins.ipnb.psi.IpnbPyParserDefinition"/>
</extensions>
<extensions defaultExtensionNs="Pythonid">
<dialectsTokenSetContributor implementation="org.jetbrains.plugins.ipnb.psi.IpnbPyTokenSetContributor"/>
</extensions>
<actions>
<action class="org.jetbrains.plugins.ipnb.editor.actions.IpnbRunCellAction" id="IpnbRunCellAction" text="Run cell">
<keyboard-shortcut keymap="$default" first-keystroke="ctrl ENTER"/>
</action>
<action class="org.jetbrains.plugins.ipnb.editor.actions.IpnbSaveAction" id="IpnbSaveAction" text="Save and Checkpoint">
</action>
<action class="org.jetbrains.plugins.ipnb.editor.actions.IpnbAddCellAction" id="IpnbAddCellAction" text="Insert Cell Below"/>
<action class="org.jetbrains.plugins.ipnb.editor.actions.IpnbCutCellAction" id="IpnbCutCellAction" text="Cut Cell"/>
<action class="org.jetbrains.plugins.ipnb.editor.actions.IpnbCopyCellAction" id="IpnbCopyCellAction" text="Copy Cell"/>
<action class="org.jetbrains.plugins.ipnb.editor.actions.IpnbPasteCellAction" id="IpnbPasteCellAction" text="Paste Cell Below"/>
</actions>
</idea-plugin>
@@ -0,0 +1,64 @@
/*
* Copyright 2000-2014 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;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.vfs.CharsetToolkit;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.PlatformIcons;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
public class IpnbFileType implements FileType {
public static final IpnbFileType INSTANCE = new IpnbFileType();
@NonNls
public static final String DEFAULT_EXTENSION = "ipynb";
@NotNull
public String getName() {
return "IPNB";
}
@NotNull
public String getDescription() {
return "IPython Notebook";
}
@NotNull
public String getDefaultExtension() {
return DEFAULT_EXTENSION;
}
public Icon getIcon() {
return PlatformIcons.UI_FORM_ICON;
} //TODO
public boolean isBinary() {
return false;
}
public boolean isReadOnly() {
return false;
}
public String getCharset(@NotNull VirtualFile file, @NotNull final byte[] content) {
return CharsetToolkit.UTF8;
}
}
@@ -0,0 +1,30 @@
/*
* Copyright 2000-2014 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;
import com.intellij.openapi.fileTypes.FileTypeConsumer;
import com.intellij.openapi.fileTypes.FileTypeFactory;
import org.jetbrains.annotations.NotNull;
/**
* @author traff
*/
public class IpnbFileTypeFactory extends FileTypeFactory {
public void createFileTypes(@NotNull FileTypeConsumer consumer) {
consumer.consume(IpnbFileType.INSTANCE);
}
}
@@ -0,0 +1,231 @@
package org.jetbrains.plugins.ipnb;
import com.intellij.ide.BrowserUtil;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.text.MarkdownUtil;
import com.petebevin.markdown.MarkdownProcessor;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Worker;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import netscape.javascript.JSException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.ipnb.editor.IpnbEditorUtil;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
public class IpnbUtils {
private static final Logger LOG = Logger.getInstance(IpnbUtils.class);
private static MarkdownProcessor ourMarkdownProcessor = new MarkdownProcessor();
private static final String ourPrefix = "<html><head><script type=\"text/x-mathjax-config\">\n" +
" MathJax.Hub.Config({\n" +
" tex2jax: {\n" +
" inlineMath: [ ['$','$'], [\"\\\\(\",\"\\\\)\"] ],\n" +
" displayMath: [ ['$$','$$'], [\"\\\\[\",\"\\\\]\"] ],\n" +
" processEscapes: true,\n" +
" processEnvironments: true\n" +
" },\n" +
" displayAlign: 'center',\n" +
" \"HTML-CSS\": {\n" +
" styles: {'.MathJax_Display': {\"margin\": 0}},\n" +
" webFont: null,\n" +
" preferredFont: null,\n" +
" linebreaks: { automatic: true }\n" +
" }\n" +
" });\n" +
"</script><script type=\"text/javascript\"\n" +
" src=\"http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML\">\n" +
"</script></head><body style='width: " + IpnbEditorUtil.PANEL_WIDTH + "px'><div id=\"mydiv\">";
public static String markdown2Html(@NotNull final String description) {
// TODO: add links to the dependant notebook pages (see: index.ipynb)
//TODO: relative picture links (see: index.ipynb in IPython.kernel) We should use absolute file:/// path
final List<String> lines = ContainerUtil.newArrayList(description.split("\n|\r|\r\n"));
final List<String> processedLines = new ArrayList<String>();
boolean isInCode = false;
for (String line : lines) {
String processedLine = line;
if (line.startsWith(" ")) {
processedLine = line.substring(1);
}
if (processedLine.contains("```")) isInCode = !isInCode;
if (isInCode) {
processedLine = processedLine
.replace("&", "&amp;");
}
else {
processedLine = processedLine
.replaceAll("([\\w])_([\\w])", "$1&underline;$2");
}
processedLines.add(processedLine);
}
MarkdownUtil.replaceHeaders(processedLines);
MarkdownUtil.removeImages(processedLines);
MarkdownUtil.generateLists(processedLines);
MarkdownUtil.replaceCodeBlock(processedLines);
final String[] lineArray = ArrayUtil.toStringArray(processedLines);
final String normalizedMarkdown = StringUtil.join(lineArray, "\n");
String html = ourMarkdownProcessor.markdown(normalizedMarkdown);
html = html
.replace("<pre><code>", "<pre>").replace("</code></pre>", "</pre>")
.replace("<em>", "<i>").replace("</em>", "</i>")
.replace("<strong>", "<b>").replace("</strong>", "</b>")
.replace("&underline;", "_")
.trim();
return html;
}
public static void addLatexToPanel(@NotNull final String source, @NotNull final JPanel panel) {
final StringBuilder result = convertToHtml(source);
addToPanel(result.toString(), panel);
}
private static StringBuilder convertToHtml(@NotNull final String source) {
final StringBuilder result = new StringBuilder();
StringBuilder markdown = new StringBuilder();
boolean inCode = false;
int inMultiStringCode = 0;
boolean inEnd = false;
boolean escaped = false;
boolean backQuoted = false;
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 ? "</pre>" : "<pre>");
escaped = !escaped;
i = i + 2;
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(markdown2Html(markdown.toString()));
markdown = new StringBuilder();
}
result.append(charAt);
}
else {
markdown.append(charAt);
}
if (inEnd && charAt == '}') {
inMultiStringCode -= 1;
}
}
if (markdown.length() != 0) {
result.append(markdown2Html(markdown.toString()));
}
return result;
}
public static void addToPanel(@NotNull final String source, @NotNull final JPanel panel) {
Platform.setImplicitExit(false);
final String text = ourPrefix + source + "</div></body></html>";
final JFXPanel javafxPanel = new JFXPanel();
javafxPanel.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
final MouseEvent parentEvent = SwingUtilities.convertMouseEvent(javafxPanel, e, panel);
panel.dispatchEvent(parentEvent);
}
});
Platform.runLater(new Runnable() {
@Override
public void run() {
final BorderPane borderPane = new BorderPane();
final WebView webComponent = new WebView();
webComponent.setPrefWidth(IpnbEditorUtil.PANEL_WIDTH + 100);
webComponent.setPrefHeight(5);
final WebEngine engine = webComponent.getEngine();
engine.locationProperty().addListener(new ChangeListener<String>() {
@Override
public void changed(ObservableValue<? extends String> value, String newValue, String t1) {
try {
final URI address = new URI(value.getValue());
BrowserUtil.browse(address);
}
catch (URISyntaxException e) {
LOG.warn(e);
}
}
});
engine.getLoadWorker().stateProperty().addListener(new ChangeListener<Worker.State>() {
@Override
public void changed(ObservableValue<? extends Worker.State> arg0, Worker.State oldState, Worker.State newState) {
if (newState == Worker.State.SUCCEEDED) {
adjustHeight(webComponent, javafxPanel, panel);
}
}
});
engine.loadContent(text);
borderPane.setCenter(webComponent);
final Scene scene = new Scene(borderPane);
javafxPanel.setScene(scene);
}
});
panel.add(javafxPanel);
}
private static void adjustHeight(final WebView webComponent, final JFXPanel javafxPanel, final JPanel panel) {
Platform.runLater(new Runnable() {
@Override
public void run() {
try {
Object result = webComponent.getEngine().executeScript("document.getElementById(\"mydiv\").offsetHeight");
if (result instanceof Integer) {
final double value = ((Integer)result + 150);
javafxPanel.setPreferredSize(new Dimension((int)webComponent.getPrefWidth(), (int)value));
panel.revalidate();
panel.repaint();
}
}
catch (JSException e) {
LOG.warn(e.getMessage());
}
}
});
}
}
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.jetbrains.plugins.ipnb.configuration.IpnbConfigurable">
<grid id="27dc6" binding="myMainPanel" layout-manager="GridLayoutManager" row-count="1" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<xy x="20" y="20" width="657" height="302"/>
</constraints>
<properties/>
<border type="none"/>
<children>
<grid id="6ed7f" layout-manager="GridLayoutManager" row-count="1" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="1" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
<border type="none"/>
<children>
<component id="1a2fb" class="com.intellij.ui.components.JBLabel">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="IPython Notebook URL:"/>
</properties>
</component>
<component id="5a5b" class="com.intellij.ui.components.JBTextField" binding="myFieldUrl">
<constraints>
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
</component>
</children>
</grid>
</children>
</grid>
</form>
@@ -0,0 +1,69 @@
package org.jetbrains.plugins.ipnb.configuration;
import com.intellij.openapi.options.ConfigurationException;
import com.intellij.openapi.options.SearchableConfigurable;
import com.intellij.openapi.project.Project;
import com.intellij.ui.components.JBTextField;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
public class IpnbConfigurable implements SearchableConfigurable {
private JPanel myMainPanel;
private JBTextField myFieldUrl;
@NotNull private final Project myProject;
public IpnbConfigurable(@NotNull Project project) {
myProject = project;
myFieldUrl.setText(IpnbSettings.getInstance(myProject).getURL());
}
@Nls
@Override
public String getDisplayName() {
return "IPython Notebook";
}
@Override
public String getHelpTopic() {
return "reference-ipnb";
}
@Override
public JComponent createComponent() {
return myMainPanel;
}
@Override
public boolean isModified() {
final String oldUrl = IpnbSettings.getInstance(myProject).getURL();
final String url = myFieldUrl.getText();
return !url.equals(oldUrl);
}
@Override
public void apply() throws ConfigurationException {
IpnbSettings.getInstance(myProject).setURL(myFieldUrl.getText());
}
@Override
public void reset() {
}
@Override
public void disposeUIResources() {
}
@NotNull
@Override
public String getId() {
return "IpnbConfigurable";
}
@Override
public Runnable enableSearch(String option) {
return null;
}
}
@@ -0,0 +1,129 @@
package org.jetbrains.plugins.ipnb.configuration;
import com.intellij.openapi.components.ProjectComponent;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.MessageType;
import com.intellij.openapi.ui.popup.Balloon;
import com.intellij.openapi.ui.popup.BalloonBuilder;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.util.text.StringUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.ipnb.editor.IpnbFileEditor;
import org.jetbrains.plugins.ipnb.editor.panels.code.IpnbCodePanel;
import org.jetbrains.plugins.ipnb.format.cells.output.IpnbOutputCell;
import org.jetbrains.plugins.ipnb.protocol.IpnbConnection;
import org.jetbrains.plugins.ipnb.protocol.IpnbConnectionListenerBase;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public final class IpnbConnectionManager implements ProjectComponent {
private static final Logger LOG = Logger.getInstance(IpnbConnectionManager.class);
private final Project myProject;
private Map<String, IpnbConnection> myKernels = new HashMap<String, IpnbConnection>();
private Map<String, IpnbCodePanel> myUpdateMap = new HashMap<String, IpnbCodePanel>();
public IpnbConnectionManager(final Project project) {
myProject = project;
}
public static IpnbConnectionManager getInstance(Project project) {
return project.getComponent(IpnbConnectionManager.class);
}
public void executeCell(@NotNull final IpnbCodePanel codePanel) {
final IpnbFileEditor fileEditor = codePanel.getFileEditor();
final String path = fileEditor.getVirtualFile().getPath();
if (!myKernels.containsKey(path)) {
try {
final String url = IpnbSettings.getInstance(myProject).getURL();
if (StringUtil.isEmptyOrSpaces(url)) {
BalloonBuilder balloonBuilder = JBPopupFactory.getInstance().createHtmlTextBalloonBuilder(
"Please, specify IPython Notebook URL in Settings->IPython Notebook", null, MessageType.WARNING.getPopupBackground(), null);
final Balloon balloon = balloonBuilder.createBalloon();
balloon.showInCenterOf(fileEditor.getRunCellButton());
return;
}
final IpnbConnection connection = new IpnbConnection(new URI(url), new IpnbConnectionListenerBase() {
@Override
public void onOpen(@NotNull IpnbConnection connection) {
final String messageId = connection.execute(codePanel.getCell().getSourceAsString());
myUpdateMap.put(messageId, codePanel);
}
@Override
public void onOutput(@NotNull IpnbConnection connection,
@NotNull String parentMessageId,
@NotNull List<IpnbOutputCell> outputs,
@Nullable Integer execCount) {
if (!myUpdateMap.containsKey(parentMessageId)) return;
final IpnbCodePanel cell = myUpdateMap.remove(parentMessageId);
cell.getCell().setPromptNumber(execCount);
cell.updatePanel(outputs);
}
});
myKernels.put(path, connection);
}
catch (IOException e) {
BalloonBuilder balloonBuilder = JBPopupFactory.getInstance().createHtmlTextBalloonBuilder(
"Please, check that IPython Notebook is running", null, MessageType.WARNING.getPopupBackground(), null);
final Balloon balloon = balloonBuilder.createBalloon();
balloon.showInCenterOf(fileEditor.getRunCellButton());
}
catch (URISyntaxException e) {
BalloonBuilder balloonBuilder = JBPopupFactory.getInstance().createHtmlTextBalloonBuilder(
"Please, check IPython Notebook URL in Settings->IPython Notebook", null, MessageType.WARNING.getPopupBackground(), null);
final Balloon balloon = balloonBuilder.createBalloon();
balloon.showInCenterOf(fileEditor.getRunCellButton());
}
}
else {
final IpnbConnection connection = myKernels.get(path);
if (connection != null) {
final String messageId = connection.execute(codePanel.getCell().getSourceAsString());
myUpdateMap.put(messageId, codePanel);
}
}
}
public void projectOpened() {}
public void projectClosed() {
shutdownKernels();
}
private void shutdownKernels() {
for (IpnbConnection connection : myKernels.values()) {
connection.shutdown();
try {
connection.close();
}
catch (IOException e) {
LOG.error(e);
}
catch (InterruptedException e) {
LOG.error(e);
}
}
myKernels.clear();
}
@NotNull
public String getComponentName() {
return "IpnbConnectionManager";
}
public void initComponent() {
}
public void disposeComponent() {
shutdownKernels();
}
}
@@ -0,0 +1,38 @@
package org.jetbrains.plugins.ipnb.configuration;
import com.intellij.openapi.components.*;
import com.intellij.openapi.project.Project;
import com.intellij.util.xmlb.XmlSerializerUtil;
import com.intellij.util.xmlb.annotations.Transient;
import org.jetbrains.annotations.NotNull;
@State(name = "IpnbSettings",
storages = {@Storage(file = StoragePathMacros.PROJECT_FILE)}
)
public class IpnbSettings implements PersistentStateComponent<IpnbSettings> {
public String URL = "";
public static IpnbSettings getInstance(@NotNull Project project) {
return ServiceManager.getService(project, IpnbSettings.class);
}
public void setURL(@NotNull final String url) {
URL = url;
}
@Transient
public String getURL() {
return URL;
}
@Override
public IpnbSettings getState() {
return this;
}
@Override
public void loadState(IpnbSettings state) {
XmlSerializerUtil.copyBean(state, this);
}
}
@@ -0,0 +1,72 @@
package org.jetbrains.plugins.ipnb.editor;
import com.intellij.openapi.fileEditor.FileEditor;
import com.intellij.openapi.fileEditor.FileEditorPolicy;
import com.intellij.openapi.fileEditor.FileEditorProvider;
import com.intellij.openapi.fileEditor.FileEditorState;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.vfs.VirtualFile;
import org.jdom.Element;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.ipnb.IpnbFileType;
/**
* @author traff
*/
public class IpnbEditorProvider implements FileEditorProvider, DumbAware {
@NonNls private static final String SELECTED_CELL = "selected";
@NonNls private static final String ID = "id";
@NonNls private static final String TOP = "top";
@Override
public boolean accept(@NotNull Project project, @NotNull VirtualFile file) {
return file.getFileType() == IpnbFileType.INSTANCE;
}
@NotNull
@Override
public FileEditor createEditor(@NotNull Project project, @NotNull VirtualFile file) {
return new IpnbFileEditor(project, file);
}
@Override
public void disposeEditor(@NotNull FileEditor editor) {
Disposer.dispose(editor);
}
@NotNull
@Override
public FileEditorState readState(@NotNull Element sourceElement, @NotNull Project project, @NotNull VirtualFile file) {
final IpnbEditorState state = new IpnbEditorState(-1, 0, 0);
final Element child = sourceElement.getChild(SELECTED_CELL);
state.setSelectedIndex(child == null ? 0 : Integer.parseInt(child.getAttributeValue(ID)));
state.setSelectedTop(child == null ? 0 : Integer.parseInt(child.getAttributeValue(TOP)));
return state;
}
@Override
public void writeState(@NotNull FileEditorState state, @NotNull Project project, @NotNull Element targetElement) {
IpnbEditorState editorState = (IpnbEditorState)state;
final int id = editorState.getSelectedIndex();
final int location = editorState.getSelectedTop();
final Element element = new Element(SELECTED_CELL);
element.setAttribute(ID, String.valueOf(id));
element.setAttribute(TOP, String.valueOf(location));
targetElement.addContent(element);
}
@NotNull
@Override
public String getEditorTypeId() {
return "ipnb-editor";
}
@NotNull
@Override
public FileEditorPolicy getPolicy() {
return FileEditorPolicy.PLACE_BEFORE_DEFAULT_EDITOR;
}
}
@@ -0,0 +1,65 @@
/*
* Copyright 2000-2009 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;
import com.intellij.openapi.fileEditor.FileEditorState;
import com.intellij.openapi.fileEditor.FileEditorStateLevel;
final class IpnbEditorState implements FileEditorState{
private final transient long myDocumentModificationStamp; // should not be serialized
private int mySelectedIndex;
private int mySelectedTop;
public IpnbEditorState(final long modificationStamp, int selectedComponentIndex, int top) {
myDocumentModificationStamp = modificationStamp;
mySelectedIndex = selectedComponentIndex;
mySelectedTop = top;
}
public boolean equals(final Object o) {
if (this == o) return true;
if (!(o instanceof IpnbEditorState)) return false;
final IpnbEditorState state = (IpnbEditorState)o;
return myDocumentModificationStamp == state.myDocumentModificationStamp && mySelectedIndex == state.mySelectedIndex &&
mySelectedTop == state.mySelectedTop;
}
public int getSelectedTop() {
return mySelectedTop;
}
public void setSelectedTop(int selectedTop) {
mySelectedTop = selectedTop;
}
public void setSelectedIndex(int selectedIndex) {
mySelectedIndex = selectedIndex;
}
public int getSelectedIndex() {
return mySelectedIndex;
}
public int hashCode(){
return (int)(myDocumentModificationStamp ^ (myDocumentModificationStamp >>> 32));
}
public boolean canBeMergedWith(FileEditorState otherState, FileEditorStateLevel level) {
return otherState instanceof IpnbEditorState;
}
}
@@ -0,0 +1,121 @@
/*
* Copyright 2000-2014 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;
import com.google.common.collect.Lists;
import com.intellij.execution.impl.ConsoleViewUtil;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.editor.colors.EditorColorsManager;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.ui.Gray;
import com.intellij.ui.JBColor;
import com.intellij.util.ui.UIUtil;
import com.jetbrains.python.PythonFileType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.ipnb.editor.panels.code.IpnbCodeSourcePanel;
import org.jetbrains.plugins.ipnb.psi.IpnbPyFragment;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseWheelListener;
import java.util.List;
/**
* @author traff
*/
public class IpnbEditorUtil {
public enum PromptType { In, Out, None }
public static Dimension PROMPT_SIZE = new Dimension(80, 30);
public static int PANEL_WIDTH = 900;
public static Editor createPythonCodeEditor(@NotNull final Project project, @NotNull final IpnbCodeSourcePanel codeSourcePanel) {
final EditorFactory editorFactory = EditorFactory.getInstance();
assert editorFactory != null;
final Document document = createPythonCodeDocument(project, codeSourcePanel);
assert document != null;
EditorEx editor = (EditorEx)editorFactory.createEditor(document, project, PythonFileType.INSTANCE, false);
setupEditor(editor);
return editor;
}
private static void setupEditor(@NotNull final EditorEx editor) {
if (!UIUtil.isUnderDarcula())
editor.setBackgroundColor(Gray._247);
noScrolling(editor);
editor.getScrollPane().setBorder(null);
ConsoleViewUtil.setupConsoleEditor(editor, false, false);
}
public static Editor createPlainCodeEditor(@NotNull final Project project, @NotNull final String text) {
final EditorFactory editorFactory = EditorFactory.getInstance();
assert editorFactory != null;
final Document document = editorFactory.createDocument(text);
EditorEx editor = (EditorEx)editorFactory.createEditor(document, project);
setupEditor(editor);
return editor;
}
private static void noScrolling(EditorEx editor) {
editor.getScrollPane().setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
editor.getScrollPane().setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
editor.getScrollPane().setWheelScrollingEnabled(false);
List<MouseWheelListener> listeners = Lists.newArrayList(editor.getScrollPane().getMouseWheelListeners());
for (MouseWheelListener l : listeners) {
editor.getScrollPane().removeMouseWheelListener(l);
}
}
public static Document createPythonCodeDocument(@NotNull final Project project, @NotNull IpnbCodeSourcePanel codeSourcePanel) {
final String text = codeSourcePanel.getCell().getSourceAsString().trim();
final IpnbPyFragment fragment = new IpnbPyFragment(project, text, true, codeSourcePanel);
return PsiDocumentManager.getInstance(project).getDocument(fragment);
}
public static JComponent createPromptComponent(Integer promptNumber, @NotNull final PromptType type) {
final String promptText = prompt(promptNumber, type);
JLabel promptLabel = new JLabel(promptText);
promptLabel.setHorizontalAlignment(SwingConstants.RIGHT);
promptLabel.setPreferredSize(PROMPT_SIZE);
final Font font = promptLabel.getFont();
assert font != null;
promptLabel.setFont(font.deriveFont(Font.BOLD));
final JBColor darkRed = new JBColor(new Color(210, 30, 50), new Color(210, 30, 50));
promptLabel.setForeground(type == PromptType.In ? JBColor.BLUE : darkRed);
promptLabel.setBackground(getBackground());
return promptLabel;
}
protected static String prompt(Integer promptNumber, @NotNull final PromptType type) {
if (type == PromptType.In)
return promptNumber == null ? type + " [ ]:" : promptNumber > 0 ? String.format(type + " [%d]:", promptNumber) : type + " [*]:";
else if (type == PromptType.Out)
return promptNumber == null ? type + "[ ]:" : promptNumber > 0 ? String.format(type + "[%d]:", promptNumber) : type + "[*]:";
return "";
}
public static Color getBackground() {
return EditorColorsManager.getInstance().getGlobalScheme().getDefaultBackground();
}
}
@@ -0,0 +1,447 @@
package org.jetbrains.plugins.ipnb.editor;
import com.google.common.collect.Lists;
import com.intellij.AppTopics;
import com.intellij.codeHighlighting.BackgroundEditorHighlighter;
import com.intellij.icons.AllIcons;
import com.intellij.ide.structureView.StructureViewBuilder;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileEditor.*;
import com.intellij.openapi.fileEditor.impl.FileEditorProviderManagerImpl;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.ComboBox;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.UserDataHolderBase;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.pom.Navigatable;
import com.intellij.ui.JBColor;
import com.intellij.ui.ScrollPaneFactory;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.ipnb.editor.actions.*;
import org.jetbrains.plugins.ipnb.editor.panels.*;
import org.jetbrains.plugins.ipnb.editor.panels.code.IpnbCodePanel;
import org.jetbrains.plugins.ipnb.format.IpnbParser;
import org.jetbrains.plugins.ipnb.format.cells.IpnbCell;
import org.jetbrains.plugins.ipnb.format.cells.IpnbCodeCell;
import org.jetbrains.plugins.ipnb.format.cells.IpnbHeadingCell;
import org.jetbrains.plugins.ipnb.format.cells.IpnbMarkdownCell;
import org.jetbrains.plugins.ipnb.format.cells.output.IpnbOutputCell;
import javax.swing.*;
import javax.swing.border.MatteBorder;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeListener;
import java.util.List;
/**
* @author traff
*/
public class IpnbFileEditor extends UserDataHolderBase implements FileEditor, TextEditor {
private final Project myProject;
private final VirtualFile myFile;
private final String myName;
private final JComponent myEditorPanel;
private final TextEditor myEditor;
private final IpnbFilePanel myIpnbFilePanel;
private ComboBox myCellTypeCombo;
private static final String codeCellType = "Code";
private static final String markdownCellType = "Markdown";
private static final String headingCellType = "Heading ";
private static final String rawNBCellType = "Raw NBConvert";
private final static String[] ourCellTypes = new String[]{codeCellType, markdownCellType, /*rawNBCellType, */headingCellType + "1",
headingCellType + "2", headingCellType + "3", headingCellType + "4", headingCellType + "5", headingCellType + "6"};
private JButton myRunCellButton;
private final JScrollPane myScrollPane;
public IpnbFileEditor(Project project, final VirtualFile vFile) {
myProject = project;
myProject.getMessageBus().connect(this).subscribe(AppTopics.FILE_DOCUMENT_SYNC, new FileDocumentManagerAdapter() {
@Override
public void beforeAllDocumentsSaving() {
final IpnbFilePanel filePanel = myIpnbFilePanel;
if (filePanel != null) {
IpnbParser.saveIpnbFile(filePanel);
vFile.refresh(false, false);
}
}
});
myProject.getMessageBus().connect(this).subscribe(FileEditorManagerListener.Before.FILE_EDITOR_MANAGER, new FileEditorManagerListener.Before.Adapter() {
@Override
public void beforeFileClosed(@NotNull FileEditorManager source, @NotNull VirtualFile file) {
IpnbParser.saveIpnbFile(myIpnbFilePanel);
file.refresh(false, false);
}
});
myFile = vFile;
myName = vFile.getName();
myEditor = createEditor(project, vFile);
myEditorPanel = new JPanel(new BorderLayout());
myEditorPanel.setBackground(IpnbEditorUtil.getBackground());
myIpnbFilePanel = createIpnbEditorPanel(myProject, vFile);
final JPanel controlPanel = createControlPanel();
myEditorPanel.add(controlPanel, BorderLayout.NORTH);
myScrollPane = ScrollPaneFactory.createScrollPane(myIpnbFilePanel);
myScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
myEditorPanel.add(myScrollPane, BorderLayout.CENTER);
}
public JScrollPane getScrollPane() {
return myScrollPane;
}
private JPanel createControlPanel() {
final JPanel controlPanel = new JPanel();
controlPanel.setBackground(IpnbEditorUtil.getBackground());
addSaveButton(controlPanel);
addAddButton(controlPanel);
addCutButton(controlPanel);
addCopyButton(controlPanel);
addPasteButton(controlPanel);
addRunButton(controlPanel);
myCellTypeCombo = new ComboBox(ourCellTypes);
myCellTypeCombo.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
final Object selectedItem = myCellTypeCombo.getSelectedItem();
final IpnbEditablePanel selectedCell = myIpnbFilePanel.getSelectedCell();
if (selectedCell != null && selectedItem instanceof String) {
updateCellType((String)selectedItem, selectedCell);
}
}
});
final IpnbPanel selectedCell = myIpnbFilePanel.getSelectedCell();
updateCellTypeCombo(selectedCell);
controlPanel.add(myCellTypeCombo);
final MatteBorder border = BorderFactory.createMatteBorder(0, 0, 1, 0, JBColor.GRAY);
controlPanel.setBorder(border);
return controlPanel;
}
private void addRunButton(@NotNull final JPanel controlPanel) {
myRunCellButton = new JButton();
myRunCellButton.setBackground(IpnbEditorUtil.getBackground());
myRunCellButton.setPreferredSize(new Dimension(30, 30));
myRunCellButton.setIcon(AllIcons.General.Run);
myRunCellButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
final IpnbRunCellAction action = (IpnbRunCellAction)ActionManager.getInstance().getAction("IpnbRunCellAction");
action.runCell(myIpnbFilePanel);
}
});
controlPanel.add(myRunCellButton);
}
private void addSaveButton(@NotNull final JPanel controlPanel) {
addButton(controlPanel, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
final IpnbSaveAction action = (IpnbSaveAction)ActionManager.getInstance().getAction("IpnbSaveAction");
action.saveAndCheckpoint(IpnbFileEditor.this);
}
}, AllIcons.Actions.Menu_saveall);
}
private void addCutButton(@NotNull final JPanel controlPanel) {
addButton(controlPanel, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
final IpnbCutCellAction action = (IpnbCutCellAction)ActionManager.getInstance().getAction("IpnbCutCellAction");
action.cutCell(myIpnbFilePanel);
}
}, AllIcons.Actions.Menu_cut);
}
private void addCopyButton(@NotNull final JPanel controlPanel) {
addButton(controlPanel, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
final IpnbCopyCellAction action = (IpnbCopyCellAction)ActionManager.getInstance().getAction("IpnbCopyCellAction");
action.copyCell(myIpnbFilePanel);
}
}, AllIcons.Actions.Copy);
}
private void addPasteButton(@NotNull final JPanel controlPanel) {
addButton(controlPanel, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
final IpnbPasteCellAction action = (IpnbPasteCellAction)ActionManager.getInstance().getAction("IpnbPasteCellAction");
action.pasteCell(myIpnbFilePanel);
}
}, AllIcons.Actions.Menu_paste);
}
private void addButton(@NotNull final JPanel controlPanel, @NotNull final ActionListener listener, @NotNull final Icon icon) {
final JButton button = new JButton();
button.setBackground(IpnbEditorUtil.getBackground());
button.setPreferredSize(new Dimension(30, 30));
button.setIcon(icon);
button.addActionListener(listener);
controlPanel.add(button);
}
private void addAddButton(@NotNull final JPanel controlPanel) {
addButton(controlPanel, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
final IpnbAddCellAction action = (IpnbAddCellAction)ActionManager.getInstance().getAction("IpnbAddCellAction");
action.addCell(myIpnbFilePanel);
}
}, AllIcons.General.Add);
}
public JButton getRunCellButton() {
return myRunCellButton;
}
private void updateCellType(@NotNull final String selectedItem, @NotNull final IpnbEditablePanel selectedCell) {
if (selectedCell instanceof IpnbHeadingPanel) {
final IpnbHeadingCell cell = ((IpnbHeadingPanel)selectedCell).getCell();
if (selectedItem.startsWith(headingCellType)) {
final char c = selectedItem.charAt(selectedItem.length() - 1);
final int level = Character.getNumericValue(c);
if (level != cell.getLevel()) {
cell.setLevel(level);
selectedCell.updateCellView();
}
}
else if (selectedItem.equals(markdownCellType)) {
final List<IpnbCell> cells = myIpnbFilePanel.getIpnbFile().getCells();
final int index = cells.indexOf(((IpnbHeadingPanel)selectedCell).getCell());
final IpnbMarkdownCell markdownCell = new IpnbMarkdownCell(cell.getSource());
if (index >= 0)
cells.set(index, markdownCell);
myIpnbFilePanel.replaceComponent(selectedCell, markdownCell);
}
else if (selectedItem.equals(codeCellType)) {
final List<IpnbCell> cells = myIpnbFilePanel.getIpnbFile().getCells();
final int index = cells.indexOf(((IpnbHeadingPanel)selectedCell).getCell());
final IpnbCodeCell codeCell = new IpnbCodeCell("python", cell.getSource(), null, Lists.<IpnbOutputCell>newArrayList());
if (index >= 0)
cells.set(index, codeCell);
myIpnbFilePanel.replaceComponent(selectedCell, codeCell);
}
}
else if (selectedCell instanceof IpnbMarkdownPanel) {
final IpnbMarkdownCell cell = ((IpnbMarkdownPanel)selectedCell).getCell();
if (selectedItem.startsWith(headingCellType)) {
final char c = selectedItem.charAt(selectedItem.length() - 1);
final int level = Character.getNumericValue(c);
final List<IpnbCell> cells = myIpnbFilePanel.getIpnbFile().getCells();
final int index = cells.indexOf(((IpnbMarkdownPanel)selectedCell).getCell());
final IpnbHeadingCell headingCell = new IpnbHeadingCell(cell.getSource(), level);
if (index >= 0)
cells.set(index, headingCell);
myIpnbFilePanel.replaceComponent(selectedCell, headingCell);
}
else if (selectedItem.equals(codeCellType)) {
final List<IpnbCell> cells = myIpnbFilePanel.getIpnbFile().getCells();
final int index = cells.indexOf(((IpnbMarkdownPanel)selectedCell).getCell());
final IpnbCodeCell codeCell = new IpnbCodeCell("python", cell.getSource(), null, Lists.<IpnbOutputCell>newArrayList());
if (index >= 0)
cells.set(index, codeCell);
myIpnbFilePanel.replaceComponent(selectedCell, codeCell);
}
}
else if (selectedCell instanceof IpnbCodePanel) {
final IpnbCodeCell cell = ((IpnbCodePanel)selectedCell).getCell();
if (selectedItem.startsWith(headingCellType)) {
final char c = selectedItem.charAt(selectedItem.length() - 1);
final int level = Character.getNumericValue(c);
final List<IpnbCell> cells = myIpnbFilePanel.getIpnbFile().getCells();
final int index = cells.indexOf(((IpnbCodePanel)selectedCell).getCell());
final IpnbHeadingCell headingCell = new IpnbHeadingCell(cell.getSource(), level);
if (index >= 0)
cells.set(index, headingCell);
myIpnbFilePanel.replaceComponent(selectedCell, headingCell);
}
else if(selectedItem.equals(markdownCellType)) {
final List<IpnbCell> cells = myIpnbFilePanel.getIpnbFile().getCells();
final int index = cells.indexOf(((IpnbCodePanel)selectedCell).getCell());
final IpnbMarkdownCell markdownCell = new IpnbMarkdownCell(cell.getSource());
if (index >= 0)
cells.set(index, markdownCell);
myIpnbFilePanel.replaceComponent(selectedCell, markdownCell);
}
}
}
@NotNull
private IpnbFilePanel createIpnbEditorPanel(Project project, VirtualFile vFile) {
return new IpnbFilePanel(project, this, vFile,
new CellSelectionListener() {
@Override
public void selectionChanged(@NotNull IpnbPanel ipnbPanel) {
if (myCellTypeCombo == null) return;
updateCellTypeCombo(ipnbPanel);
}
});
}
private void updateCellTypeCombo(IpnbPanel ipnbPanel) {
if (ipnbPanel instanceof IpnbHeadingPanel) {
final IpnbHeadingCell cell = ((IpnbHeadingPanel)ipnbPanel).getCell();
final int level = cell.getLevel();
myCellTypeCombo.setSelectedItem(headingCellType + level);
}
else if (ipnbPanel instanceof IpnbMarkdownPanel) {
myCellTypeCombo.setSelectedItem(markdownCellType);
}
else if (ipnbPanel instanceof IpnbCodePanel) {
myCellTypeCombo.setSelectedItem(codeCellType);
}
}
public IpnbFilePanel getIpnbFilePanel() {
return myIpnbFilePanel;
}
@NotNull
@Override
public JComponent getComponent() {
return myEditorPanel;
}
@Override
public JComponent getPreferredFocusedComponent() {
return myEditorPanel;
}
@NotNull
@Override
public String getName() {
return myName;
}
@NotNull
@Override
public FileEditorState getState(@NotNull FileEditorStateLevel level) {
final int index = getIpnbFilePanel().getSelectedIndex();
final IpnbEditablePanel cell = getIpnbFilePanel().getSelectedCell();
final int top = cell != null ? cell.getTop() : 0;
return new IpnbEditorState(-1, index, top);
}
@Override
public void setState(@NotNull FileEditorState state) {
final int index = ((IpnbEditorState)state).getSelectedIndex();
final int position = ((IpnbEditorState)state).getSelectedTop();
myIpnbFilePanel.setInitialPosition(index, position);
}
@Override
public boolean isModified() {
return false;
}
@Override
public boolean isValid() {
return true;
}
@Override
public void selectNotify() {
}
@Override
public void deselectNotify() {
}
@Override
public void addPropertyChangeListener(@NotNull PropertyChangeListener listener) {
}
@Override
public void removePropertyChangeListener(@NotNull PropertyChangeListener listener) {
}
@Override
public BackgroundEditorHighlighter getBackgroundHighlighter() {
return null;
}
@Override
public FileEditorLocation getCurrentLocation() {
return null;
}
@Override
public StructureViewBuilder getStructureViewBuilder() {
return null;
}
@Override
public void dispose() {
Disposer.dispose(myEditor);
}
@NotNull
@Override
public Editor getEditor() {
return myEditor.getEditor();
}
@Override
public boolean canNavigateTo(@NotNull Navigatable navigatable) {
return true;
}
@Override
public void navigateTo(@NotNull Navigatable navigatable) {
}
@Nullable
private static TextEditor createEditor(@NotNull Project project, @NotNull VirtualFile vFile) {
FileEditorProvider provider = getProvider(project, vFile);
if (provider != null) {
FileEditor editor = provider.createEditor(project, vFile);
if (editor instanceof TextEditor) {
return (TextEditor)editor;
}
}
return null;
}
@Nullable
private static FileEditorProvider getProvider(Project project, VirtualFile vFile) {
FileEditorProvider[] providers = FileEditorProviderManagerImpl.getInstance().getProviders(project, vFile);
for (FileEditorProvider provider : providers) {
if (!(provider instanceof IpnbEditorProvider)) {
return provider;
}
}
return null;
}
public abstract class CellSelectionListener {
public abstract void selectionChanged(@NotNull final IpnbPanel ipnbPanel);
}
public VirtualFile getVirtualFile() {
return myFile;
}
}
@@ -0,0 +1,31 @@
package org.jetbrains.plugins.ipnb.editor.actions;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.actionSystem.PlatformDataKeys;
import com.intellij.openapi.fileEditor.FileEditor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.ipnb.editor.IpnbFileEditor;
import org.jetbrains.plugins.ipnb.editor.panels.IpnbFilePanel;
public class IpnbAddCellAction extends AnAction {
public IpnbAddCellAction() {
super(AllIcons.General.Run);
}
@Override
public void actionPerformed(AnActionEvent event) {
final DataContext context = event.getDataContext();
final FileEditor editor = PlatformDataKeys.FILE_EDITOR.getData(context);
if (editor instanceof IpnbFileEditor) {
final IpnbFilePanel component = ((IpnbFileEditor)editor).getIpnbFilePanel();
addCell(component);
}
}
public void addCell(@NotNull final IpnbFilePanel ipnbFilePanel) {
ipnbFilePanel.createAndAddCell();
}
}
@@ -0,0 +1,31 @@
package org.jetbrains.plugins.ipnb.editor.actions;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.actionSystem.PlatformDataKeys;
import com.intellij.openapi.fileEditor.FileEditor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.ipnb.editor.IpnbFileEditor;
import org.jetbrains.plugins.ipnb.editor.panels.IpnbFilePanel;
public class IpnbCopyCellAction extends AnAction {
public IpnbCopyCellAction() {
super(AllIcons.General.Run);
}
@Override
public void actionPerformed(AnActionEvent event) {
final DataContext context = event.getDataContext();
final FileEditor editor = PlatformDataKeys.FILE_EDITOR.getData(context);
if (editor instanceof IpnbFileEditor) {
final IpnbFilePanel component = ((IpnbFileEditor)editor).getIpnbFilePanel();
copyCell(component);
}
}
public void copyCell(@NotNull final IpnbFilePanel ipnbFilePanel) {
ipnbFilePanel.copyCell();
}
}
@@ -0,0 +1,33 @@
package org.jetbrains.plugins.ipnb.editor.actions;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.actionSystem.PlatformDataKeys;
import com.intellij.openapi.fileEditor.FileEditor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.ipnb.editor.IpnbFileEditor;
import org.jetbrains.plugins.ipnb.editor.panels.IpnbFilePanel;
public class IpnbCutCellAction extends AnAction {
public IpnbCutCellAction() {
super(AllIcons.General.Run);
}
@Override
public void actionPerformed(AnActionEvent event) {
final DataContext context = event.getDataContext();
final FileEditor editor = PlatformDataKeys.FILE_EDITOR.getData(context);
if (editor instanceof IpnbFileEditor) {
final IpnbFilePanel component = ((IpnbFileEditor)editor).getIpnbFilePanel();
cutCell(component);
}
}
public void cutCell(@NotNull final IpnbFilePanel ipnbFilePanel) {
ipnbFilePanel.cutCell();
ipnbFilePanel.revalidate();
ipnbFilePanel.repaint();
}
}
@@ -0,0 +1,31 @@
package org.jetbrains.plugins.ipnb.editor.actions;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.actionSystem.PlatformDataKeys;
import com.intellij.openapi.fileEditor.FileEditor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.ipnb.editor.IpnbFileEditor;
import org.jetbrains.plugins.ipnb.editor.panels.IpnbFilePanel;
public class IpnbPasteCellAction extends AnAction {
public IpnbPasteCellAction() {
super(AllIcons.General.Run);
}
@Override
public void actionPerformed(AnActionEvent event) {
final DataContext context = event.getDataContext();
final FileEditor editor = PlatformDataKeys.FILE_EDITOR.getData(context);
if (editor instanceof IpnbFileEditor) {
final IpnbFilePanel component = ((IpnbFileEditor)editor).getIpnbFilePanel();
pasteCell(component);
}
}
public void pasteCell(@NotNull final IpnbFilePanel ipnbFilePanel) {
ipnbFilePanel.pasteCell();
}
}
@@ -0,0 +1,37 @@
package org.jetbrains.plugins.ipnb.editor.actions;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.actionSystem.PlatformDataKeys;
import com.intellij.openapi.fileEditor.FileEditor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.ipnb.editor.IpnbFileEditor;
import org.jetbrains.plugins.ipnb.editor.panels.IpnbEditablePanel;
import org.jetbrains.plugins.ipnb.editor.panels.IpnbFilePanel;
public class IpnbRunCellAction extends AnAction {
public IpnbRunCellAction() {
super(AllIcons.General.Run);
}
@Override
public void actionPerformed(AnActionEvent event) {
final DataContext context = event.getDataContext();
final FileEditor editor = PlatformDataKeys.FILE_EDITOR.getData(context);
if (editor instanceof IpnbFileEditor) {
final IpnbFilePanel component = ((IpnbFileEditor)editor).getIpnbFilePanel();
runCell(component);
}
}
public void runCell(@NotNull final IpnbFilePanel ipnbFilePanel) {
final IpnbEditablePanel cell = ipnbFilePanel.getSelectedCell();
cell.runCell();
ipnbFilePanel.selectNext(cell);
ipnbFilePanel.revalidate();
ipnbFilePanel.repaint();
ipnbFilePanel.requestFocus();
}
}
@@ -0,0 +1,35 @@
package org.jetbrains.plugins.ipnb.editor.actions;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.actionSystem.PlatformDataKeys;
import com.intellij.openapi.fileEditor.FileEditor;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.ipnb.editor.IpnbFileEditor;
import org.jetbrains.plugins.ipnb.editor.panels.IpnbFilePanel;
import org.jetbrains.plugins.ipnb.format.IpnbParser;
public class IpnbSaveAction extends AnAction {
public IpnbSaveAction() {
super(AllIcons.General.Run);
}
@Override
public void actionPerformed(AnActionEvent event) {
final DataContext context = event.getDataContext();
final FileEditor editor = PlatformDataKeys.FILE_EDITOR.getData(context);
if (editor instanceof IpnbFileEditor) {
saveAndCheckpoint((IpnbFileEditor)editor);
}
}
public void saveAndCheckpoint(@NotNull final IpnbFileEditor editor) {
final IpnbFilePanel filePanel = editor.getIpnbFilePanel();
IpnbParser.saveIpnbFile(filePanel);
final VirtualFile file = editor.getVirtualFile();
file.refresh(false, false);
}
}
@@ -0,0 +1,161 @@
package org.jetbrains.plugins.ipnb.editor.panels;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.ui.Gray;
import com.intellij.ui.JBColor;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.ipnb.format.cells.IpnbEditableCell;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
public abstract class IpnbEditablePanel<T extends JComponent, K extends IpnbEditableCell> extends IpnbPanel<T, K> {
private static final Logger LOG = Logger.getInstance(IpnbEditablePanel.class);
private boolean myEditing;
protected JTextArea myEditablePanel;
public final static String EDITABLE_PANEL = "Editable panel";
public final static String VIEW_PANEL = "View panel";
public IpnbEditablePanel(@NotNull K cell) {
super(cell);
}
protected void initPanel() {
myViewPanel = createViewPanel();
myViewPanel.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
final Container parent = getParent();
final MouseEvent parentEvent = SwingUtilities.convertMouseEvent(myViewPanel, e, parent);
parent.dispatchEvent(parentEvent);
if (e.getClickCount() == 2) {
switchToEditing();
}
}
});
myViewPanel.setName(VIEW_PANEL);
add(myViewPanel, VIEW_PANEL);
myEditablePanel = createEditablePanel();
myEditablePanel.setName(EDITABLE_PANEL);
add(myEditablePanel, EDITABLE_PANEL);
}
public void switchToEditing() {
setEditing(true);
final LayoutManager layout = getLayout();
if (layout instanceof CardLayout) {
((CardLayout)layout).show(this, EDITABLE_PANEL);
UIUtil.requestFocus(myEditablePanel);
}
}
public boolean isModified() {
final Component[] components = getComponents();
for (Component component : components) {
final String name = component.getName();
if (component.isVisible() && EDITABLE_PANEL.equals(name)) return true;
}
return false;
}
protected String getRawCellText() { return ""; }
public void runCell() {
final LayoutManager layout = getLayout();
if (layout instanceof CardLayout) {
updateCellSource();
updateCellView();
((CardLayout)layout).show(this, VIEW_PANEL);
setEditing(false);
}
}
private JTextArea createEditablePanel() {
final JTextArea textArea = new JTextArea(getRawCellText());
textArea.setLineWrap(true);
textArea.setEditable(true);
textArea.setBorder(BorderFactory.createLineBorder(JBColor.lightGray));
textArea.setBackground(Gray._247);
textArea.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 1) {
setEditing(true);
final Container parent = getParent();
parent.repaint();
if (parent instanceof IpnbFilePanel) {
((IpnbFilePanel)parent).setSelectedCell(IpnbEditablePanel.this);
textArea.requestFocus();
}
}
}
});
textArea.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
setEditing(false);
final Container parent = getParent();
if (parent instanceof IpnbFilePanel) {
parent.repaint();
UIUtil.requestFocus((IpnbFilePanel)parent);
}
}
}
});
return textArea;
}
public boolean contains(int y) {
return y>= getTop() && y<=getBottom();
}
public int getTop() {
return getY();
}
public int getBottom() {
return getTop() + getHeight();
}
public boolean isEditing() {
return myEditing;
}
public void setEditing(boolean editing) {
myEditing = editing;
}
public void updateCellView() { // TODO: make abstract
}
public void updateCellSource() {
final String text = myEditablePanel.getText();
myCell.setSource(StringUtil.splitByLinesKeepSeparators(text != null ? text : ""));
}
@SuppressWarnings("CloneDoesntDeclareCloneNotSupportedException")
@Override
protected Object clone() {
try {
return super.clone();
}
catch (CloneNotSupportedException e) {
LOG.error(e);
}
return null;
}
public K getCell() {
return myCell;
}
}
@@ -0,0 +1,25 @@
/*
* Copyright 2000-2014 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.panels;
import com.intellij.openapi.editor.Editor;
/**
* @author traff
*/
public interface IpnbEditorPanel {
Editor getEditor();
}
@@ -0,0 +1,424 @@
package org.jetbrains.plugins.ipnb.editor.panels;
import com.google.common.collect.Lists;
import com.intellij.openapi.actionSystem.DataProvider;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.JBColor;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.ipnb.editor.IpnbEditorUtil;
import org.jetbrains.plugins.ipnb.editor.IpnbFileEditor;
import org.jetbrains.plugins.ipnb.editor.panels.code.IpnbCodePanel;
import org.jetbrains.plugins.ipnb.format.IpnbFile;
import org.jetbrains.plugins.ipnb.format.IpnbParser;
import org.jetbrains.plugins.ipnb.format.cells.*;
import org.jetbrains.plugins.ipnb.format.cells.output.IpnbOutputCell;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class IpnbFilePanel extends JPanel implements Scrollable, DataProvider {
public static final int INSET_Y = 10;
public static final int INSET_X = 5;
private IpnbFile myIpnbFile;
private Project myProject;
@NotNull private IpnbFileEditor myParent;
@NotNull private final IpnbFileEditor.CellSelectionListener myListener;
private final List<IpnbEditablePanel> myIpnbPanels = Lists.newArrayList();
private IpnbEditablePanel mySelectedCell;
private IpnbEditablePanel myBufferPanel;
private int myIncrement = 10;
private int myInitialSelection = 0;
private int myInitialPosition = 0;
public IpnbFilePanel(@NotNull final Project project, @NotNull final IpnbFileEditor parent, @NotNull final VirtualFile vFile,
@NotNull final IpnbFileEditor.CellSelectionListener listener) {
super(new GridBagLayout());
myProject = project;
myParent = parent;
myListener = listener;
setBackground(IpnbEditorUtil.getBackground());
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
try {
myIpnbFile = IpnbParser.parseIpnbFile(vFile);
layoutFile();
addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
updateCellSelection(e);
}
});
setFocusable(true);
}
catch (IOException e) {
Messages.showErrorDialog(project, e.getMessage(), "Can't open " + vFile.getPath());
}
}
});
UIUtil.requestFocus(this);
}
public List<IpnbEditablePanel> getIpnbPanels() {
return myIpnbPanels;
}
private void layoutFile() {
GridBagConstraints c = new GridBagConstraints();
c.fill = GridBagConstraints.HORIZONTAL;
c.gridx = 0;
c.gridy = 0;
c.gridwidth = 2;
c.insets = new Insets(INSET_Y, INSET_X, 0, 0);
final int width = IpnbEditorUtil.PANEL_WIDTH + IpnbEditorUtil.PROMPT_SIZE.width;
final JLabel label = new JLabel("<html><body style='width: " + width + "px'></body></html>");
add(label, c);
c.gridy = 1;
c.gridwidth = 1;
final JPanel panel = new JPanel();
panel.setPreferredSize(IpnbEditorUtil.PROMPT_SIZE);
panel.setBackground(getBackground());
panel.setOpaque(false);
add(panel, c);
final List<IpnbCell> cells = myIpnbFile.getCells();
for (IpnbCell cell : cells) {
c.gridy = addCellToPanel(cell, c);
}
if (myInitialSelection >= 0 && myIpnbPanels.size() > myInitialSelection) {
final IpnbEditablePanel toSelect = myIpnbPanels.get(myInitialSelection);
setSelectedCell(toSelect);
myParent.getScrollPane().getViewport().setViewPosition(new Point(0, myInitialPosition));
}
c.weighty = 1;
add(createEmptyPanel(), c);
}
private int addCellToPanel(IpnbCell cell, GridBagConstraints c) {
IpnbEditablePanel panel;
if (cell instanceof IpnbCodeCell) {
panel = new IpnbCodePanel(myProject, myParent, (IpnbCodeCell)cell);
c.gridwidth = 2;
c.gridx = 0;
add(panel, c);
myIpnbPanels.add(panel);
}
else if (cell instanceof IpnbMarkdownCell) {
panel = new IpnbMarkdownPanel((IpnbMarkdownCell)cell);
addComponent(c, panel);
}
else if (cell instanceof IpnbHeadingCell) {
panel = new IpnbHeadingPanel((IpnbHeadingCell)cell);
addComponent(c, panel);
}
else {
throw new UnsupportedOperationException(cell.getClass().toString());
}
return c.gridy + 1;
}
public void createAndAddCell() {
removeAll();
final IpnbCodeCell cell = new IpnbCodeCell("python", new String[]{""}, null, new ArrayList<IpnbOutputCell>());
final IpnbCodePanel codePanel = new IpnbCodePanel(myProject, myParent, cell);
addCell(cell, codePanel);
}
private void addCell(IpnbEditableCell cell, IpnbEditablePanel panel) {
final IpnbEditablePanel selectedCell = getSelectedCell();
final int index = myIpnbPanels.indexOf(selectedCell);
myIpnbFile.addCell(cell, index+1);
myIpnbPanels.add(index + 1, panel);
final GridBagConstraints c = new GridBagConstraints();
c.fill = GridBagConstraints.HORIZONTAL;
c.gridx = 0;
c.gridy = 0;
c.gridwidth = 1;
c.insets = new Insets(INSET_Y, INSET_X, 0, 0);
final JPanel promptPanel = new JPanel();
promptPanel.setPreferredSize(new Dimension(IpnbEditorUtil.PROMPT_SIZE.width, 1));
promptPanel.setBackground(getBackground());
promptPanel.setOpaque(false);
add(promptPanel, c);
c.gridy += 1;
for (IpnbPanel comp : myIpnbPanels) {
c.gridwidth = 1;
c.gridx = 1;
if (comp instanceof IpnbCodePanel) {
c.gridwidth = 2;
c.gridx = 0;
add(comp, c);
}
else {
add(comp, c);
}
c.gridy += 1;
}
c.weighty = 1;
add(createEmptyPanel(), c);
setSelectedCell(panel);
requestFocus();
revalidate();
repaint();
}
public void cutCell() {
myBufferPanel = getSelectedCell();
selectNextOrPrev(myBufferPanel);
final int index = myIpnbPanels.indexOf(myBufferPanel);
if (index < 0) return;
myIpnbPanels.remove(index);
myIpnbFile.removeCell(index);
remove(myBufferPanel);
}
public void copyCell() {
myBufferPanel = getSelectedCell();
}
public void pasteCell() {
if (myBufferPanel == null) return;
removeAll();
final IpnbEditablePanel editablePanel = (IpnbEditablePanel)myBufferPanel.clone();
addCell(editablePanel.getCell(), editablePanel);
}
public void replaceComponent(@NotNull final IpnbEditablePanel from, @NotNull final IpnbCell cell) {
final GridBagConstraints c = ((GridBagLayout)getLayout()).getConstraints(from);
final int index = myIpnbPanels.indexOf(from);
IpnbEditablePanel panel;
if (cell instanceof IpnbCodeCell) {
panel = new IpnbCodePanel(myProject, myParent, (IpnbCodeCell)cell);
c.gridwidth = 2;
c.gridx = 0;
add(panel, c);
}
else if (cell instanceof IpnbMarkdownCell) {
panel = new IpnbMarkdownPanel((IpnbMarkdownCell)cell);
c.gridwidth = 1;
c.gridx = 1;
add(panel, c);
}
else if (cell instanceof IpnbHeadingCell) {
panel = new IpnbHeadingPanel((IpnbHeadingCell)cell);
c.gridwidth = 1;
c.gridx = 1;
add(panel, c);
}
else {
throw new UnsupportedOperationException(cell.getClass().toString());
}
if (index >= 0) {
myIpnbPanels.remove(index);
myIpnbPanels.add(index, panel);
}
setSelectedCell(panel);
remove(from);
revalidate();
repaint();
}
private void addComponent(@NotNull final GridBagConstraints c, @NotNull final IpnbEditablePanel comp) {
c.gridwidth = 1;
c.gridx = 1;
add(comp, c);
myIpnbPanels.add(comp);
}
private JPanel createEmptyPanel() {
JPanel panel = new JPanel();
panel.setBackground(IpnbEditorUtil.getBackground());
return panel;
}
@Override
protected void processKeyEvent(KeyEvent e) {
if (mySelectedCell != null && e.getID() == KeyEvent.KEY_PRESSED) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
mySelectedCell.switchToEditing();
}
int index = myIpnbPanels.indexOf(mySelectedCell);
final Rectangle rect = getVisibleRect();
if (e.getKeyCode() == KeyEvent.VK_UP) {
selectPrev(mySelectedCell);
if (index > 0) {
final Rectangle cellBounds = mySelectedCell.getBounds();
if (cellBounds.getY() + cellBounds.getHeight() <= rect.getY()) {
myIncrement = rect.y - cellBounds.y;
getParent().dispatchEvent(e);
}
}
}
else if (e.getKeyCode() == KeyEvent.VK_DOWN) {
selectNext(mySelectedCell);
if (index < myIpnbPanels.size() - 1) {
final Rectangle cellBounds = mySelectedCell.getBounds();
if (cellBounds.getY() > rect.getY() + rect.getHeight()) {
myIncrement = cellBounds.y + cellBounds.height - rect.y - rect.height;
getParent().dispatchEvent(e);
}
}
}
else {
getParent().dispatchEvent(e);
}
}
}
public void selectPrev(@NotNull IpnbEditablePanel cell) {
int index = myIpnbPanels.indexOf(cell);
if (index > 0) {
setSelectedCell(myIpnbPanels.get(index - 1));
}
}
public void selectNext(@NotNull IpnbEditablePanel cell) {
int index = myIpnbPanels.indexOf(cell);
if (index < myIpnbPanels.size() - 1) {
setSelectedCell(myIpnbPanels.get(index + 1));
}
}
public void selectNextOrPrev(@NotNull IpnbEditablePanel cell) {
int index = myIpnbPanels.indexOf(cell);
if (index < myIpnbPanels.size() - 1) {
setSelectedCell(myIpnbPanels.get(index + 1));
}
else if (index > 0) {
setSelectedCell(myIpnbPanels.get(index - 1));
}
else {
mySelectedCell = null;
repaint();
}
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (mySelectedCell != null) {
g.setColor(mySelectedCell.isEditing() ? JBColor.GREEN : JBColor.GRAY);
g.drawRoundRect(100, mySelectedCell.getTop() - 1, getWidth() - 200, mySelectedCell.getHeight() + 2, 5, 5);
}
}
private void updateCellSelection(MouseEvent e) {
if (e.getClickCount() > 0) {
IpnbEditablePanel ipnbPanel = getIpnbPanelByClick(e.getPoint());
if (ipnbPanel != null) {
ipnbPanel.setEditing(false);
ipnbPanel.requestFocus();
repaint();
setSelectedCell(ipnbPanel);
}
}
}
public void setInitialPosition(int index, int position) {
myInitialSelection = index;
myInitialPosition = position;
}
public void setSelectedCell(@NotNull final IpnbEditablePanel ipnbPanel) {
if (ipnbPanel.equals(mySelectedCell)) return;
if (mySelectedCell != null)
mySelectedCell.setEditing(false);
mySelectedCell = ipnbPanel;
revalidate();
UIUtil.requestFocus(this);
repaint();
myListener.selectionChanged(ipnbPanel);
}
public IpnbEditablePanel getSelectedCell() {
return mySelectedCell;
}
public int getSelectedIndex() {
final IpnbEditablePanel selectedCell = getSelectedCell();
return myIpnbPanels.indexOf(selectedCell);
}
@Nullable
private IpnbEditablePanel getIpnbPanelByClick(@NotNull final Point point) {
for (IpnbEditablePanel c: myIpnbPanels) {
if (c.contains(point.y)) {
return c;
}
}
return null;
}
public IpnbFile getIpnbFile() {
return myIpnbFile;
}
@Override
public Dimension getPreferredScrollableViewportSize() {
return null;
}
@Override
public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
return myIncrement;
}
@Override
public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
return 100;
}
@Override
public boolean getScrollableTracksViewportWidth() {
return false;
}
@Override
public boolean getScrollableTracksViewportHeight() {
return false;
}
@Nullable
@Override
public Object getData(String dataId) {
final IpnbEditablePanel cell = getSelectedCell();
if (OpenFileDescriptor.NAVIGATE_IN_EDITOR.is(dataId)) {
if (cell instanceof IpnbCodePanel) {
return ((IpnbCodePanel)cell).getEditor();
}
}
return null;
}
}
@@ -0,0 +1,44 @@
package org.jetbrains.plugins.ipnb.editor.panels;
import com.intellij.ui.components.JBLabel;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.ipnb.editor.IpnbEditorUtil;
import org.jetbrains.plugins.ipnb.format.cells.IpnbHeadingCell;
public class IpnbHeadingPanel extends IpnbEditablePanel<JBLabel, IpnbHeadingCell> {
public IpnbHeadingPanel(@NotNull final IpnbHeadingCell cell) {
super(cell);
initPanel();
}
@Override
protected String getRawCellText() {
return myCell.getSourceAsString();
}
private String renderCellText() {
return "<html><body style='width: " + IpnbEditorUtil.PANEL_WIDTH + "px'><h" + myCell.getLevel() + ">" + myCell.getSourceAsString() + "</h" + myCell.getLevel() +
"></body></html>";
}
@Override
public void updateCellView() {
myViewPanel.setText(renderCellText());
}
@Override
protected JBLabel createViewPanel() {
final JBLabel label = new JBLabel(renderCellText());
label.setBackground(IpnbEditorUtil.getBackground());
label.setOpaque(true);
return label;
}
@SuppressWarnings("CloneDoesntCallSuperClone")
@Override
protected Object clone() {
return new IpnbHeadingPanel((IpnbHeadingCell)myCell.clone());
}
}
@@ -0,0 +1,49 @@
package org.jetbrains.plugins.ipnb.editor.panels;
import com.intellij.openapi.ui.VerticalFlowLayout;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.ipnb.IpnbUtils;
import org.jetbrains.plugins.ipnb.editor.IpnbEditorUtil;
import org.jetbrains.plugins.ipnb.format.cells.IpnbMarkdownCell;
import javax.swing.*;
import java.awt.*;
public class IpnbMarkdownPanel extends IpnbEditablePanel<JPanel, IpnbMarkdownCell> {
public IpnbMarkdownPanel(@NotNull final IpnbMarkdownCell cell) {
super(cell);
initPanel();
}
@Override
protected String getRawCellText() {
return myCell.getSourceAsString();
}
@Override
protected JPanel createViewPanel() {
final JPanel panel = new JPanel(new VerticalFlowLayout(FlowLayout.LEFT, false, true));
updatePanel(panel);
panel.setBackground(IpnbEditorUtil.getBackground());
panel.setOpaque(true);
return panel;
}
private void updatePanel(@NotNull final JPanel panel) {
panel.removeAll();
IpnbUtils.addLatexToPanel(myCell.getSourceAsString(), panel);
}
@Override
public void updateCellView() {
updatePanel(myViewPanel);
}
@SuppressWarnings("CloneDoesntCallSuperClone")
@Override
protected Object clone() {
return new IpnbMarkdownPanel((IpnbMarkdownCell)myCell.clone());
}
}
@@ -0,0 +1,23 @@
package org.jetbrains.plugins.ipnb.editor.panels;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.ipnb.format.cells.IpnbCell;
import javax.swing.*;
import java.awt.*;
public abstract class IpnbPanel<T extends JComponent, K extends IpnbCell> extends JPanel {
protected T myViewPanel;
protected K myCell;
public IpnbPanel(@NotNull final K cell) {
super(new CardLayout());
myCell = cell;
}
public K getCell() {
return myCell;
}
protected abstract T createViewPanel();
}
@@ -0,0 +1,22 @@
package org.jetbrains.plugins.ipnb.editor.panels.code;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.ipnb.editor.panels.IpnbPanel;
import org.jetbrains.plugins.ipnb.format.cells.output.IpnbOutputCell;
import javax.swing.*;
public class IpnbCodeOutputPanel<K extends IpnbOutputCell> extends IpnbPanel<JComponent, K> {
public IpnbCodeOutputPanel(@NotNull final K cell) {
super(cell);
myViewPanel = createViewPanel();
add(myViewPanel);
}
protected JComponent createViewPanel() {
JTextArea textArea = new JTextArea(myCell.getSourceAsString());
textArea.setEditable(false);
return textArea;
}
}
@@ -0,0 +1,191 @@
package org.jetbrains.plugins.ipnb.editor.panels.code;
import com.google.common.collect.Lists;
import com.intellij.openapi.Disposable;
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.openapi.util.Disposer;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.ipnb.configuration.IpnbConnectionManager;
import org.jetbrains.plugins.ipnb.editor.IpnbEditorUtil;
import org.jetbrains.plugins.ipnb.editor.IpnbFileEditor;
import org.jetbrains.plugins.ipnb.editor.panels.IpnbEditablePanel;
import org.jetbrains.plugins.ipnb.editor.panels.IpnbPanel;
import org.jetbrains.plugins.ipnb.format.cells.IpnbCodeCell;
import org.jetbrains.plugins.ipnb.format.cells.output.*;
import javax.swing.*;
import java.awt.*;
import java.util.List;
public class IpnbCodePanel extends IpnbEditablePanel<JComponent, IpnbCodeCell> {
private final Project myProject;
private final Disposable myParent;
private IpnbCodeSourcePanel myCodeSourcePanel;
private final List<IpnbPanel> myOutputPanels = Lists.newArrayList();
public IpnbCodePanel(@NotNull final Project project, @Nullable final Disposable parent, @NotNull final IpnbCodeCell cell) {
super(cell);
myProject = project;
myParent = parent;
myViewPanel = createViewPanel();
add(myViewPanel);
}
public IpnbFileEditor getFileEditor() {
assert myParent instanceof IpnbFileEditor;
return (IpnbFileEditor)myParent;
}
public Editor getEditor() {
return myCodeSourcePanel.getEditor();
}
public void addPromptPanel(@NotNull final JComponent parent, Integer promptNumber,
@NotNull final IpnbEditorUtil.PromptType promptType,
@NotNull final IpnbPanel component, @NotNull final GridBagConstraints c) {
c.gridx = 0;
c.weightx = 0;
c.anchor = GridBagConstraints.NORTHWEST;
final JComponent promptComponent = IpnbEditorUtil.createPromptComponent(promptNumber, promptType);
c.insets = new Insets(2,2,2,5);
parent.add(promptComponent, c);
c.gridx = 1;
c.weightx = 1;
c.insets = new Insets(2,2,2,2);
c.anchor = GridBagConstraints.CENTER;
parent.add(component, c);
myOutputPanels.add(component);
}
@Override
protected JComponent createViewPanel() {
final JPanel panel = new JPanel(new GridBagLayout());
panel.setBackground(IpnbEditorUtil.getBackground());
final GridBagConstraints c = new GridBagConstraints();
c.fill = GridBagConstraints.HORIZONTAL;
c.gridx = 0;
c.gridy = 0;
c.gridwidth = 1;
myCodeSourcePanel = new IpnbCodeSourcePanel(myProject, this, myCell);
if (myParent != null)
Disposer.register(myParent, new Disposable() {
@Override
public void dispose() {
EditorFactory.getInstance().releaseEditor(myCodeSourcePanel.getEditor());
}
});
addPromptPanel(panel, myCell.getPromptNumber(), IpnbEditorUtil.PromptType.In, myCodeSourcePanel, c);
c.gridx = 1;
c.gridy = 0;
for (IpnbOutputCell outputCell : myCell.getCellOutputs()) {
c.gridy++;
addOutputPanel(panel, c, outputCell, true);
}
return panel;
}
private void addOutputPanel(@NotNull final JComponent panel, @NotNull final GridBagConstraints c,
@NotNull final IpnbOutputCell outputCell, boolean addPrompt) {
final IpnbEditorUtil.PromptType promptType = addPrompt ? IpnbEditorUtil.PromptType.Out : IpnbEditorUtil.PromptType.None;
if (outputCell instanceof IpnbImageOutputCell) {
addPromptPanel(panel, myCell.getPromptNumber(), promptType,
new IpnbImagePanel((IpnbImageOutputCell)outputCell), c);
}
else if (outputCell instanceof IpnbHtmlOutputCell) {
addPromptPanel(panel, myCell.getPromptNumber(), promptType,
new IpnbHtmlPanel((IpnbHtmlOutputCell)outputCell), c);
}
else if (outputCell instanceof IpnbLatexOutputCell) {
addPromptPanel(panel, myCell.getPromptNumber(), promptType,
new IpnbLatexPanel((IpnbLatexOutputCell)outputCell), c);
}
else if (outputCell instanceof IpnbErrorOutputCell) {
addPromptPanel(panel, myCell.getPromptNumber(), promptType,
new IpnbErrorPanel((IpnbErrorOutputCell)outputCell), c);
}
else if (outputCell instanceof IpnbStreamOutputCell) {
addPromptPanel(panel, myCell.getPromptNumber(), IpnbEditorUtil.PromptType.None,
new IpnbStreamPanel((IpnbStreamOutputCell)outputCell), c);
}
else if (outputCell.getSourceAsString() != null) {
addPromptPanel(panel, myCell.getPromptNumber(), promptType,
new IpnbCodeOutputPanel<IpnbOutputCell>(outputCell), c);
}
}
@Override
public void switchToEditing() {
setEditing(true);
getParent().repaint();
UIUtil.requestFocus(myCodeSourcePanel.getEditor().getContentComponent());
}
@Override
public void runCell() {
super.runCell();
updateCellSource();
myCell.setPromptNumber(-1);
updatePanel(myCell.getCellOutputs());
final IpnbConnectionManager connectionManager = IpnbConnectionManager.getInstance(myProject);
connectionManager.executeCell(this);
}
@Override
public boolean isModified() {
return true;
}
@Override
public void updateCellSource() {
final Document document = myCodeSourcePanel.getEditor().getDocument();
final String text = document.getText();
myCell.setSource(StringUtil.splitByLinesKeepSeparators(text));
}
public void updatePanel(@NotNull final List<IpnbOutputCell> outputContent) {
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
myCell.removeCellOutputs();
myViewPanel.removeAll();
final GridBagConstraints c = new GridBagConstraints();
c.fill = GridBagConstraints.HORIZONTAL;
c.gridx = 0;
c.gridy = 0;
c.gridwidth = 1;
addPromptPanel(myViewPanel, myCell.getPromptNumber(), IpnbEditorUtil.PromptType.In, myCodeSourcePanel, c);
for (IpnbOutputCell output : outputContent) {
myCell.addCellOutput(output);
c.gridx = 0;
c.gridy += 1;
addOutputPanel(myViewPanel, c, output, output instanceof IpnbOutOutputCell);
}
revalidate();
repaint();
}
});
}
@SuppressWarnings({"CloneDoesntCallSuperClone", "CloneDoesntDeclareCloneNotSupportedException"})
@Override
protected Object clone() {
return new IpnbCodePanel(myProject, myParent, (IpnbCodeCell)myCell.clone());
}
}
@@ -0,0 +1,123 @@
/*
* Copyright 2000-2014 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.panels.code;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.VerticalFlowLayout;
import com.intellij.ui.Gray;
import com.intellij.ui.JBColor;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.ipnb.editor.IpnbEditorUtil;
import org.jetbrains.plugins.ipnb.editor.actions.IpnbRunCellAction;
import org.jetbrains.plugins.ipnb.editor.panels.IpnbEditorPanel;
import org.jetbrains.plugins.ipnb.editor.panels.IpnbFilePanel;
import org.jetbrains.plugins.ipnb.editor.panels.IpnbPanel;
import org.jetbrains.plugins.ipnb.format.cells.IpnbCodeCell;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
/**
* @author traff
*/
public class IpnbCodeSourcePanel extends IpnbPanel<JComponent, IpnbCodeCell> implements IpnbEditorPanel {
private Editor myEditor;
@NotNull private final Project myProject;
@NotNull private final IpnbCodePanel myParent;
@NotNull private final String mySource;
public IpnbCodeSourcePanel(@NotNull final Project project, @NotNull final IpnbCodePanel parent, @NotNull final IpnbCodeCell cell) {
super(cell);
myProject = project;
myParent = parent;
mySource = cell.getSourceAsString();
final JComponent panel = createViewPanel();
add(panel);
}
@NotNull
public IpnbCodePanel getIpnbCodePanel() {
return myParent;
}
@Override
@NotNull
public Editor getEditor() {
return myEditor;
}
@Override
protected JComponent createViewPanel() {
final JPanel panel = new JPanel(new VerticalFlowLayout(FlowLayout.LEFT, true, true));
panel.setBackground(UIUtil.isUnderDarcula() ? IpnbEditorUtil.getBackground() : Gray._247);
if (mySource.startsWith("%")) {
myEditor = IpnbEditorUtil.createPlainCodeEditor(myProject, mySource);
}
else {
myEditor = IpnbEditorUtil.createPythonCodeEditor(myProject, this);
}
final JComponent component = myEditor.getComponent();
final JComponent contentComponent = myEditor.getContentComponent();
contentComponent.addKeyListener(new KeyAdapter() {
@Override
public void keyReleased(KeyEvent e) {
final int keyCode = e.getKeyCode();
final int height = myEditor.getLineHeight() * Math.max(myEditor.getDocument().getLineCount(), 1);
component.setPreferredSize(new Dimension(IpnbEditorUtil.PANEL_WIDTH, height));
final Container parent = myParent.getParent();
if (parent instanceof IpnbFilePanel) {
IpnbFilePanel ipnbFilePanel = (IpnbFilePanel)parent;
ipnbFilePanel.revalidate();
ipnbFilePanel.repaint();
if (keyCode == KeyEvent.VK_ESCAPE) {
getIpnbCodePanel().setEditing(false);
UIUtil.requestFocus(getIpnbCodePanel().getFileEditor().getIpnbFilePanel());
}
else if (keyCode == KeyEvent.VK_ENTER && InputEvent.CTRL_DOWN_MASK == e.getModifiersEx()) {
final IpnbRunCellAction action = (IpnbRunCellAction)ActionManager.getInstance().getAction("IpnbRunCellAction");
action.runCell(ipnbFilePanel);
}
}
}
});
contentComponent.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (InputEvent.CTRL_DOWN_MASK == e.getModifiersEx()) return;
final Container ipnbFilePanel = myParent.getParent();
if (ipnbFilePanel instanceof IpnbFilePanel) {
((IpnbFilePanel)ipnbFilePanel).setSelectedCell(myParent);
myParent.switchToEditing();
}
UIUtil.requestFocus(contentComponent);
}
});
panel.add(component);
component.setPreferredSize(new Dimension(IpnbEditorUtil.PANEL_WIDTH, component.getPreferredSize().height));
setBorder(BorderFactory.createLineBorder(JBColor.lightGray, 1, true));
return panel;
}
}
@@ -0,0 +1,138 @@
package org.jetbrains.plugins.ipnb.editor.panels.code;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.ui.JBColor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.ipnb.editor.IpnbEditorUtil;
import org.jetbrains.plugins.ipnb.format.cells.output.IpnbErrorOutputCell;
import javax.swing.*;
import javax.swing.text.AttributeSet;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyleContext;
import java.awt.*;
public class IpnbErrorPanel extends IpnbCodeOutputPanel<IpnbErrorOutputCell> {
public IpnbErrorPanel(@NotNull final IpnbErrorOutputCell cell) {
super(cell);
}
@Override
protected JComponent createViewPanel() {
final String[] text = myCell.getText();
if (text == null) return new JLabel();
ColorPane ansiColoredPane = new ColorPane();
ansiColoredPane.appendANSI(StringUtil.join(text));
ansiColoredPane.setBackground(IpnbEditorUtil.getBackground());
ansiColoredPane.setEditable(false);
return ansiColoredPane;
}
public static class ColorPane extends JTextPane {
static final Color D_Red = Color.decode("#8B0000");
static final Color D_Magenta = JBColor.MAGENTA;
static final Color D_Green = Color.decode("#006400");
static final Color D_Yellow = Color.decode("#A52A2A");
static final Color D_Cyan = Color.decode("#5AB4EB");
static final Color cReset = JBColor.BLACK;
static Color currentColor = cReset;
String remaining = "";
public void append(Color color, String s) {
StyleContext styleContext = StyleContext.getDefaultStyleContext();
AttributeSet attributeSet = styleContext.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, color);
int len = getDocument().getLength();
setCaretPosition(len);
setCharacterAttributes(attributeSet, false);
replaceSelection(s);
}
public void appendANSI(String string) {
int position = 0;
int index;
int mIndex;
String substring;
boolean continueSearch;
String addString = remaining + string;
remaining = "";
if (addString.length() > 0) {
index = addString.indexOf("\u001B");
if (index == -1) {
append(currentColor, addString);
return;
}
if (index > 0) {
substring = addString.substring(0, index);
append(currentColor, substring);
position = index;
}
continueSearch = true;
while (continueSearch) {
mIndex = addString.indexOf("m", position);
if (mIndex < 0) {
remaining = addString.substring(position, addString.length());
continueSearch = false;
continue;
}
else {
substring = addString.substring(position, mIndex + 1);
currentColor = getANSIColor(substring);
}
position = mIndex + 1;
index = addString.indexOf("\u001B", position);
if (index == -1) {
substring = addString.substring(position, addString.length());
append(currentColor, substring);
continueSearch = false;
continue;
}
substring = addString.substring(position, index);
position = index;
append(currentColor, substring);
}
}
}
public static Color getANSIColor(String ANSIColor) {
if (ANSIColor.equals("\u001B[30m") || ANSIColor.equals("\u001B[0;30m") || ANSIColor.equals("\u001B[1;30m")) {
return JBColor.BLACK;
}
else if (ANSIColor.equals("\u001B[31m") || ANSIColor.equals("\u001B[0;31m") || ANSIColor.equals("\u001B[1;31m")) {
return D_Red;
}
else if (ANSIColor.equals("\u001B[32m") || ANSIColor.equals("\u001B[0;32m") || ANSIColor.equals("\u001B[1;32m")) {
return D_Green;
}
else if (ANSIColor.equals("\u001B[33m") || ANSIColor.equals("\u001B[0;33m") || ANSIColor.equals("\u001B[1;33m")) {
return D_Yellow;
}
else if (ANSIColor.equals("\u001B[34m") || ANSIColor.equals("\u001B[0;34m") || ANSIColor.equals("\u001B[1;34m")) {
return JBColor.BLUE;
}
else if (ANSIColor.equals("\u001B[35m") || ANSIColor.equals("\u001B[0;35m") || ANSIColor.equals("\u001B[1;35m")) {
return D_Magenta;
}
else if (ANSIColor.equals("\u001B[36m") || ANSIColor.equals("\u001B[0;36m") || ANSIColor.equals("\u001B[1;36m")) {
return D_Cyan;
}
else if (ANSIColor.equals("\u001B[37m") || ANSIColor.equals("\u001B[0;37m") || ANSIColor.equals("\u001B[1;37m")) {
return JBColor.WHITE;
}
else if (ANSIColor.equals("\u001B[0m")) {
return cReset;
}
else {
return JBColor.WHITE;
}
}
}
}
@@ -0,0 +1,45 @@
package org.jetbrains.plugins.ipnb.editor.panels.code;
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.web.WebView;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.ipnb.format.cells.output.IpnbHtmlOutputCell;
import javax.swing.*;
public class IpnbHtmlPanel extends IpnbCodeOutputPanel<IpnbHtmlOutputCell> {
public IpnbHtmlPanel(@NotNull final IpnbHtmlOutputCell cell) {
super(cell);
}
@Override
protected JComponent createViewPanel() {
Platform.setImplicitExit(false);
final JFXPanel javafxPanel = new JFXPanel();
final StringBuilder text = new StringBuilder("<html>");
for (String html : myCell.getHtmls()) {
html = html.replace("\"", "'");
text.append(html);
}
text.append("</html>");
Platform.runLater(new Runnable() {
@Override
public void run() {
BorderPane borderPane = new BorderPane();
WebView webComponent = new WebView();
webComponent.getEngine().loadContent(text.toString());
borderPane.setCenter(webComponent);
Scene scene = new Scene(borderPane, 450, 450);
javafxPanel.setScene(scene);
}
});
return javafxPanel;
}
}
@@ -0,0 +1,42 @@
package org.jetbrains.plugins.ipnb.editor.panels.code;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.ui.components.JBLabel;
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.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
public class IpnbImagePanel extends IpnbCodeOutputPanel<IpnbImageOutputCell> {
private static final Logger LOG = Logger.getInstance(IpnbImagePanel.class);
public IpnbImagePanel(@NotNull final IpnbImageOutputCell cell) {
super(cell);
}
@Override
protected JComponent createViewPanel() {
final String png = myCell.getBase64String();
final JBLabel label = new JBLabel();
try {
byte[] btDataFile = new BASE64Decoder().decodeBuffer(png);
BufferedImage image = ImageIO.read(new ByteArrayInputStream(btDataFile));
label.setIcon(new ImageIcon(image));
}
catch (IOException e) {
LOG.error("Couldn't parse image. " + e.getMessage());
}
label.setBackground(IpnbEditorUtil.getBackground());
label.setOpaque(true);
return label;
}
}
@@ -0,0 +1,29 @@
package org.jetbrains.plugins.ipnb.editor.panels.code;
import com.intellij.openapi.ui.VerticalFlowLayout;
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.format.cells.output.IpnbLatexOutputCell;
import javax.swing.*;
import java.awt.*;
public class IpnbLatexPanel extends IpnbCodeOutputPanel<IpnbLatexOutputCell> {
public IpnbLatexPanel(@NotNull final IpnbLatexOutputCell cell) {
super(cell);
setLayout(new VerticalFlowLayout(FlowLayout.LEFT));
}
@Override
protected JComponent createViewPanel() {
final JPanel panel = new JPanel();
IpnbUtils.addLatexToPanel(StringUtil.join(myCell.getLatex()), panel);
setBackground(IpnbEditorUtil.getBackground());
panel.setBackground(IpnbEditorUtil.getBackground());
panel.setOpaque(true);
setOpaque(true);
return panel;
}
}
@@ -0,0 +1,10 @@
package org.jetbrains.plugins.ipnb.editor.panels.code;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.ipnb.format.cells.output.IpnbStreamOutputCell;
public class IpnbStreamPanel extends IpnbCodeOutputPanel<IpnbStreamOutputCell> {
public IpnbStreamPanel(@NotNull final IpnbStreamOutputCell cell) {
super(cell);
}
}
@@ -0,0 +1,38 @@
package org.jetbrains.plugins.ipnb.format;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.ipnb.format.cells.IpnbCell;
import java.util.List;
public class IpnbFile {
private final IpnbParser.IpnbFileRaw myRawFile;
private final List<IpnbCell> myCells;
private final String myPath;
IpnbFile(IpnbParser.IpnbFileRaw rawFile, List<IpnbCell> cells, String path) {
myRawFile = rawFile;
myCells = cells;
myPath = path;
}
public List<IpnbCell> getCells() {
return myCells;
}
public void addCell(@NotNull final IpnbCell cell, int index) {
myCells.add(index, cell);
}
public void removeCell(int index) {
myCells.remove(index);
}
public String getPath() {
return myPath;
}
public IpnbParser.IpnbFileRaw getRawFile() {
return myRawFile;
}
}
@@ -0,0 +1,253 @@
package org.jetbrains.plugins.ipnb.format;
import com.google.common.collect.Lists;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.vfs.CharsetToolkit;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.ipnb.editor.panels.IpnbEditablePanel;
import org.jetbrains.plugins.ipnb.editor.panels.IpnbFilePanel;
import org.jetbrains.plugins.ipnb.format.cells.*;
import org.jetbrains.plugins.ipnb.format.cells.output.*;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class IpnbParser {
private static final Logger LOG = Logger.getInstance(IpnbParser.class);
private static final Gson gson = initGson();
@NotNull
private static Gson initGson() {
final GsonBuilder builder = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping();
return builder.create();
}
@NotNull
public static IpnbFile parseIpnbFile(@NotNull String fileText, String path) throws IOException {
IpnbFileRaw rawFile = gson.fromJson(fileText, IpnbFileRaw.class);
if (rawFile == null) return new IpnbFile(new IpnbFileRaw(), Lists.<IpnbCell>newArrayList(), path);
List<IpnbCell> cells = new ArrayList<IpnbCell>();
final IpnbWorksheet[] worksheets = rawFile.worksheets;
for (IpnbWorksheet worksheet : worksheets) {
final IpnbCellRaw[] rawCells = worksheet.cells;
for (IpnbCellRaw rawCell : rawCells) {
cells.add(rawCell.createCell());
}
}
return new IpnbFile(rawFile, cells, path);
}
@NotNull
public static IpnbFile parseIpnbFile(@NotNull VirtualFile virtualFile) throws IOException {
final String fileText = new String(virtualFile.contentsToByteArray(), CharsetToolkit.UTF8);
return parseIpnbFile(fileText, virtualFile.getPath());
}
public static void saveIpnbFile(@NotNull final IpnbFilePanel ipnbPanel) {
final IpnbFile ipnbFile = ipnbPanel.getIpnbFile();
if (ipnbFile == null) return;
for (IpnbEditablePanel panel : ipnbPanel.getIpnbPanels()) {
if (panel.isModified()) {
panel.updateCellSource();
}
}
final IpnbFileRaw fileRaw = ipnbFile.getRawFile();
final IpnbWorksheet worksheet = new IpnbWorksheet();
final ArrayList<IpnbCellRaw> cellRaws = new ArrayList<IpnbCellRaw>();
for (IpnbCell cell: ipnbFile.getCells()) {
cellRaws.add(IpnbCellRaw.fromCell(cell));
}
worksheet.cells = cellRaws.toArray(new IpnbCellRaw[cellRaws.size()]);
fileRaw.worksheets = new IpnbWorksheet[]{worksheet};
final String json = gson.toJson(fileRaw);
final String path = ipnbFile.getPath();
final File file = new File(path);
FileWriter writer = null;
try {
writer = new FileWriter(file);
writer.write(json);
} catch (IOException e) {
LOG.error(e);
}
finally {
try {
if (writer != null)
writer.close();
} catch (IOException e) {
LOG.error(e);
}
}
}
public static class IpnbFileRaw {
Map<String, String> metadata;
int nbformat;
int nbformat_minor;
IpnbWorksheet[] worksheets;
}
private static class IpnbWorksheet {
IpnbCellRaw[] cells;
}
private static class IpnbCellRaw {
String cell_type;
Integer level;
String[] source;
String[] input;
String language;
CellOutputRaw[] outputs;
Integer prompt_number;
public static IpnbCellRaw fromCell(@NotNull final IpnbCell cell) {
final IpnbCellRaw raw = new IpnbCellRaw();
if (cell instanceof IpnbMarkdownCell) {
raw.cell_type = "markdown";
raw.source = ((IpnbMarkdownCell)cell).getSource();
}
else if (cell instanceof IpnbCodeCell) {
raw.cell_type = "code";
final ArrayList<CellOutputRaw> outputRaws = new ArrayList<CellOutputRaw>();
for (IpnbOutputCell outputCell : ((IpnbCodeCell)cell).getCellOutputs()) {
outputRaws.add(CellOutputRaw.fromOutput(outputCell));
}
raw.outputs = outputRaws.toArray(new CellOutputRaw[outputRaws.size()]);
raw.language = ((IpnbCodeCell)cell).getLanguage();
raw.input = ((IpnbCodeCell)cell).getSource();
raw.prompt_number = ((IpnbCodeCell)cell).getPromptNumber();
}
else if (cell instanceof IpnbRawCell) {
raw.cell_type = "raw";
}
else if (cell instanceof IpnbHeadingCell) {
raw.cell_type = "heading";
raw.source = ((IpnbHeadingCell)cell).getSource();
raw.level = ((IpnbHeadingCell)cell).getLevel();
}
return raw;
}
public IpnbCell createCell() {
final IpnbCell cell;
if (cell_type.equals("markdown")) {
cell = new IpnbMarkdownCell(source);
}
else if (cell_type.equals("code")) {
final List<IpnbOutputCell> outputCells = new ArrayList<IpnbOutputCell>();
for (CellOutputRaw outputRaw : outputs) {
outputCells.add(outputRaw.createOutput());
}
cell = new IpnbCodeCell(language, input, prompt_number, outputCells);
}
else if (cell_type.equals("raw")) {
cell = new IpnbRawCell();
}
else if (cell_type.equals("heading")) {
cell = new IpnbHeadingCell(source, level);
}
else {
cell = null;
}
return cell;
}
}
private static class CellOutputRaw {
String ename;
String evalue;
String output_type;
String png;
String stream;
String jpeg;
String[] html;
String[] latex;
String[] svg;
Integer prompt_number;
String[] text;
String[] traceback;
public static CellOutputRaw fromOutput(@NotNull final IpnbOutputCell outputCell) {
final CellOutputRaw raw = new CellOutputRaw();
if (outputCell instanceof IpnbPngOutputCell) {
raw.png = ((IpnbPngOutputCell)outputCell).getBase64String();
raw.text = outputCell.getText();
//raw.output_type = "display_data";
}
else if (outputCell instanceof IpnbSvgOutputCell) {
raw.svg = ((IpnbSvgOutputCell)outputCell).getSvg();
raw.text = outputCell.getText();
}
else if (outputCell instanceof IpnbJpegOutputCell) {
raw.jpeg = ((IpnbJpegOutputCell)outputCell).getBase64String();
raw.text = outputCell.getText();
}
else if (outputCell instanceof IpnbLatexOutputCell) {
raw.latex = ((IpnbLatexOutputCell)outputCell).getLatex();
raw.prompt_number = outputCell.getPromptNumber();
raw.text = outputCell.getText();
}
else if (outputCell instanceof IpnbStreamOutputCell) {
raw.stream = ((IpnbStreamOutputCell)outputCell).getStream();
raw.output_type = "stream";
raw.text = outputCell.getText();
}
else if (outputCell instanceof IpnbHtmlOutputCell) {
raw.html = ((IpnbHtmlOutputCell)outputCell).getHtmls();
raw.text = outputCell.getText();
}
else if (outputCell instanceof IpnbErrorOutputCell) {
raw.output_type = "pyerr";
raw.evalue = ((IpnbErrorOutputCell)outputCell).getEvalue();
raw.ename = ((IpnbErrorOutputCell)outputCell).getEname();
raw.traceback = outputCell.getText();
}
else if (outputCell instanceof IpnbOutOutputCell) {
raw.output_type = "pyout";
raw.text = outputCell.getText();
raw.prompt_number = outputCell.getPromptNumber();
}
return raw;
}
public IpnbOutputCell createOutput() {
final IpnbOutputCell outputCell;
if (png != null) {
outputCell = new IpnbPngOutputCell(png, text, prompt_number);
}
else if (jpeg != null) {
outputCell = new IpnbJpegOutputCell(jpeg, text, prompt_number);
}
else if (svg != null) {
outputCell = new IpnbSvgOutputCell(svg, text, prompt_number);
}
else if (latex != null) {
outputCell = new IpnbLatexOutputCell(latex, prompt_number, text);
}
else if (stream != null) {
outputCell = new IpnbStreamOutputCell(stream, text, prompt_number);
}
else if (html != null) {
outputCell = new IpnbHtmlOutputCell(html, text, prompt_number);
}
else if ("pyerr".equals(output_type)) {
outputCell = new IpnbErrorOutputCell(evalue, ename, traceback, prompt_number);
}
else if ("pyout".equals(output_type)) {
outputCell = new IpnbOutOutputCell(text, prompt_number);
}
else {
outputCell = new IpnbOutputCell(text, prompt_number);
}
return outputCell;
}
}
}
@@ -0,0 +1,4 @@
package org.jetbrains.plugins.ipnb.format.cells;
public interface IpnbCell {
}
@@ -0,0 +1,58 @@
package org.jetbrains.plugins.ipnb.format.cells;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.ipnb.format.cells.output.IpnbOutputCell;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class IpnbCodeCell extends IpnbEditableCell {
@NotNull private final String myLanguage;
@Nullable private Integer myPromptNumber;
@NotNull private final List<IpnbOutputCell> myCellOutputs;
public IpnbCodeCell(@NotNull final String language,
@NotNull final String[] input,
@Nullable final Integer number,
@NotNull final List<IpnbOutputCell> cellOutputs) {
super(input);
myLanguage = language;
myPromptNumber = number;
myCellOutputs = cellOutputs;
}
@NotNull
public String getLanguage() {
return myLanguage;
}
@Nullable
public Integer getPromptNumber() {
return myPromptNumber;
}
public void setPromptNumber(@Nullable Integer promptNumber) {
myPromptNumber = promptNumber;
}
@NotNull
public List<IpnbOutputCell> getCellOutputs() {
return myCellOutputs;
}
public void removeCellOutputs() {
myCellOutputs.clear();
}
public void addCellOutput(@NotNull final IpnbOutputCell cellOutput) {
myCellOutputs.add(cellOutput);
}
@SuppressWarnings({"CloneDoesntCallSuperClone", "CloneDoesntDeclareCloneNotSupportedException"})
@Override
public Object clone() {
return new IpnbCodeCell(myLanguage, Arrays.copyOf(getSource(), getSource().length), myPromptNumber, new ArrayList<IpnbOutputCell>(myCellOutputs));
}
}
@@ -0,0 +1,26 @@
package org.jetbrains.plugins.ipnb.format.cells;
import com.intellij.openapi.util.text.StringUtil;
import org.jetbrains.annotations.NotNull;
public abstract class IpnbEditableCell implements IpnbCell {
@NotNull private String[] mySource;
IpnbEditableCell(@NotNull final String[] source) {
mySource = source;
}
@NotNull
public String[] getSource() {
return mySource;
}
public void setSource(@NotNull final String[] source) {
mySource = source;
}
@NotNull
public String getSourceAsString() {
return StringUtil.join(mySource);
}
}
@@ -0,0 +1,26 @@
package org.jetbrains.plugins.ipnb.format.cells;
import org.jetbrains.annotations.NotNull;
public class IpnbHeadingCell extends IpnbEditableCell {
private int myLevel;
public IpnbHeadingCell(@NotNull final String[] source, int level) {
super(source);
myLevel = level;
}
public int getLevel() {
return myLevel;
}
public void setLevel(int level) {
myLevel = level;
}
@SuppressWarnings({"CloneDoesntCallSuperClone", "CloneDoesntDeclareCloneNotSupportedException"})
@Override
public Object clone() {
return new IpnbHeadingCell(getSource(), myLevel);
}
}
@@ -0,0 +1,15 @@
package org.jetbrains.plugins.ipnb.format.cells;
import org.jetbrains.annotations.NotNull;
public class IpnbMarkdownCell extends IpnbEditableCell {
public IpnbMarkdownCell(@NotNull final String[] source) {
super(source);
}
@SuppressWarnings({"CloneDoesntCallSuperClone", "CloneDoesntDeclareCloneNotSupportedException"})
@Override
public Object clone() {
return new IpnbMarkdownCell(getSource());
}
}
@@ -0,0 +1,4 @@
package org.jetbrains.plugins.ipnb.format.cells;
public class IpnbRawCell implements IpnbCell {
}
@@ -0,0 +1,27 @@
package org.jetbrains.plugins.ipnb.format.cells.output;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class IpnbErrorOutputCell extends IpnbOutputCell {
@NotNull private final String myEvalue;
@NotNull private final String myEname;
public IpnbErrorOutputCell(@NotNull final String evalue, @NotNull final String ename, @NotNull final String[] traceback,
@Nullable final Integer prompt) {
super(traceback, prompt);
myEvalue = evalue;
myEname = ename;
}
@NotNull
public String getEvalue() {
return myEvalue;
}
@NotNull
public String getEname() {
return myEname;
}
}
@@ -0,0 +1,18 @@
package org.jetbrains.plugins.ipnb.format.cells.output;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class IpnbHtmlOutputCell extends IpnbOutputCell {
@NotNull private final String[] myHtml;
public IpnbHtmlOutputCell(@NotNull final String[] html, String[] text, @Nullable final Integer prompt) {
super(text, prompt);
myHtml = html;
}
@NotNull
public String[] getHtmls() {
return myHtml;
}
}
@@ -0,0 +1,18 @@
package org.jetbrains.plugins.ipnb.format.cells.output;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class IpnbImageOutputCell extends IpnbOutputCell {
@NotNull private final String myBase64String;
public IpnbImageOutputCell(@NotNull final String base64String, @Nullable final String[] text, @Nullable final Integer prompt) {
super(text, prompt);
myBase64String = base64String;
}
@NotNull
public String getBase64String() {
return myBase64String;
}
}
@@ -0,0 +1,12 @@
package org.jetbrains.plugins.ipnb.format.cells.output;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class IpnbJpegOutputCell extends IpnbImageOutputCell {
public IpnbJpegOutputCell(@NotNull final String jpeg, @Nullable final String[] text, @Nullable final Integer prompt) {
super(jpeg, text, prompt);
}
}
@@ -0,0 +1,17 @@
package org.jetbrains.plugins.ipnb.format.cells.output;
import org.jetbrains.annotations.NotNull;
public class IpnbLatexOutputCell extends IpnbOutputCell {
@NotNull private final String[] myLatex;
public IpnbLatexOutputCell(@NotNull final String[] latex, Integer promptNumber, @NotNull final String[] text) {
super(text, promptNumber);
myLatex = latex;
}
@NotNull
public String[] getLatex() {
return myLatex;
}
}
@@ -0,0 +1,9 @@
package org.jetbrains.plugins.ipnb.format.cells.output;
import org.jetbrains.annotations.Nullable;
public class IpnbOutOutputCell extends IpnbOutputCell {
public IpnbOutOutputCell(@Nullable final String[] text, Integer promptNumber) {
super(text, promptNumber);
}
}
@@ -0,0 +1,30 @@
package org.jetbrains.plugins.ipnb.format.cells.output;
import com.intellij.openapi.util.text.StringUtil;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.ipnb.format.cells.IpnbCell;
public class IpnbOutputCell implements IpnbCell {
@Nullable protected final Integer myPromptNumber;
@Nullable private final String[] myText;
public IpnbOutputCell(@Nullable final String[] text, @Nullable final Integer promptNumber) {
myText = text;
myPromptNumber = promptNumber;
}
@Nullable
public String[] getText() {
return myText;
}
@Nullable
public String getSourceAsString() {
return myText == null ? null : StringUtil.join(myText, "\n");
}
@Nullable
public Integer getPromptNumber() {
return myPromptNumber;
}
}
@@ -0,0 +1,12 @@
package org.jetbrains.plugins.ipnb.format.cells.output;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class IpnbPngOutputCell extends IpnbImageOutputCell {
public IpnbPngOutputCell(@NotNull final String png, @Nullable final String[] text, @Nullable final Integer prompt) {
super(png, text, prompt);
}
}
@@ -0,0 +1,18 @@
package org.jetbrains.plugins.ipnb.format.cells.output;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class IpnbStreamOutputCell extends IpnbOutputCell {
@NotNull private final String myStream;
public IpnbStreamOutputCell(@NotNull final String stream, String[] text, @Nullable final Integer prompt) {
super(text, prompt);
myStream = stream;
}
@NotNull
public String getStream() {
return myStream;
}
}
@@ -0,0 +1,18 @@
package org.jetbrains.plugins.ipnb.format.cells.output;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class IpnbSvgOutputCell extends IpnbOutputCell {
@NotNull private final String[] mySvg;
public IpnbSvgOutputCell(@NotNull final String[] svg, @Nullable final String[] text, @Nullable final Integer prompt) {
super(text, prompt);
mySvg = svg;
}
@NotNull
public String[] getSvg() {
return mySvg;
}
}
@@ -0,0 +1,434 @@
package org.jetbrains.plugins.ipnb.protocol;
import com.google.gson.*;
import com.intellij.openapi.util.text.StringUtil;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.drafts.Draft;
import org.java_websocket.drafts.Draft_17;
import org.java_websocket.handshake.ClientHandshakeBuilder;
import org.java_websocket.handshake.ServerHandshake;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.ipnb.format.cells.output.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.*;
import java.util.*;
/**
* @author vlan
*/
public class IpnbConnection {
private static final String API_URL = "/api";
private static final String KERNELS_URL = API_URL + "/kernels";
private static final String HTTP_POST = "POST";
public static final String HTTP_DELETE = "DELETE";
@NotNull private final URI myURI;
@NotNull private final String myKernelId;
@NotNull private final String mySessionId;
@NotNull private final IpnbConnectionListener myListener;
@NotNull private final WebSocketClient myShellClient;
@NotNull private final WebSocketClient myIOPubClient;
@NotNull private final Thread myShellThread;
@NotNull private final Thread myIOPubThread;
private volatile boolean myIsShellOpen = false;
private volatile boolean myIsIOPubOpen = false;
private volatile boolean myIsOpened = false;
public IpnbConnection(@NotNull URI uri, @NotNull IpnbConnectionListener listener) throws IOException, URISyntaxException {
myURI = uri;
myListener = listener;
mySessionId = UUID.randomUUID().toString();
myKernelId = startKernel();
final Draft draft = new Draft17WithOrigin();
// TODO: Serialize cookies for the authentication message
final String authMessage = "identity:foo";
myShellClient = new WebSocketClient(getShellURI(), draft) {
@Override
public void onOpen(@NotNull ServerHandshake handshakeData) {
send(authMessage);
myIsShellOpen = true;
notifyOpen();
}
@Override
public void onMessage(@NotNull String message) {
}
@Override
public void onClose(int code, @NotNull String reason, boolean remote) {
}
@Override
public void onError(@NotNull Exception e) {
}
};
myShellThread = new Thread(myShellClient);
myShellThread.start();
myIOPubClient = new WebSocketClient(getIOPubURI(), draft) {
private ArrayList<IpnbOutputCell> myOutput = new ArrayList<IpnbOutputCell>();
private Integer myExecCount = null;
@Override
public void onOpen(ServerHandshake handshakeData) {
send(authMessage);
myIsIOPubOpen = true;
notifyOpen();
}
@Override
public void onMessage(String message) {
final Gson gson = new Gson();
final Message msg = gson.fromJson(message, Message.class);
final Header header = msg.getHeader();
final Header parentHeader = gson.fromJson(msg.getParentHeader(), Header.class);
final String messageType = header.getMessageType();
if ("pyout".equals(messageType) || "display_data".equals(messageType)) {
final PyOutContent content = gson.fromJson(msg.getContent(), PyOutContent.class);
addCellOutput(content, myOutput);
}
else if ("pyerr".equals(messageType)) {
final PyErrContent content = gson.fromJson(msg.getContent(), PyErrContent.class);
addCellOutput(content, myOutput);
}
else if ("stream".equals(messageType)) {
final PyStreamContent content = gson.fromJson(msg.getContent(), PyStreamContent.class);
addCellOutput(content, myOutput);
}
else if ("pyin".equals(messageType)) {
final JsonElement executionCount = msg.getContent().get("execution_count");
if (executionCount != null) {
myExecCount = executionCount.getAsInt();
}
}
else if ("status".equals(messageType)) {
final PyStatusContent content = gson.fromJson(msg.getContent(), PyStatusContent.class);
if (content.getExecutionState().equals("idle")) {
//noinspection unchecked
myListener.onOutput(IpnbConnection.this, parentHeader.getMessageId(), (List<IpnbOutputCell>)myOutput.clone(), myExecCount);
myOutput.clear();
}
}
}
@Override
public void onClose(int code, String reason, boolean remote) {
}
@Override
public void onError(Exception ex) {
}
};
myIOPubThread = new Thread(myIOPubClient);
myIOPubThread.start();
}
private void notifyOpen() {
if (!myIsOpened && myIsShellOpen && myIsIOPubOpen) {
myIsOpened = true;
myListener.onOpen(this);
}
}
@NotNull
public String execute(@NotNull String code) {
final String messageId = UUID.randomUUID().toString();
myShellClient.send(new Gson().toJson(createExecuteRequest(code, messageId)));
return messageId;
}
public void shutdown() {
myIOPubClient.close();
myShellClient.close();
}
public void close() throws IOException, InterruptedException {
myIOPubThread.join();
myShellThread.join();
shutdownKernel();
}
@NotNull
public String getKernelId() {
return myKernelId;
}
@NotNull
private String startKernel() throws IOException {
final String s = httpRequest(myURI + KERNELS_URL, HTTP_POST);
final Gson gson = new Gson();
final Kernel kernel = gson.fromJson(s, Kernel.class);
return kernel.getId();
}
private void shutdownKernel() throws IOException {
httpRequest(myURI + KERNELS_URL + "/" + myKernelId, HTTP_DELETE);
}
@NotNull
public URI getShellURI() throws URISyntaxException {
return new URI(getWebSocketURIBase() + "/shell");
}
@NotNull
public URI getIOPubURI() throws URISyntaxException {
return new URI(getWebSocketURIBase() + "/iopub");
}
@NotNull
private String getWebSocketURIBase() {
return "ws://" + myURI.getAuthority() + KERNELS_URL + "/" + myKernelId;
}
@NotNull
private static String httpRequest(@NotNull String url, @NotNull String method) throws IOException {
final URLConnection urlConnection = new URL(url).openConnection();
if (urlConnection instanceof HttpURLConnection) {
final HttpURLConnection connection = (HttpURLConnection)urlConnection;
connection.setRequestMethod(method);
final BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), "utf-8"));
try {
final StringBuilder builder = new StringBuilder();
char[] buffer = new char[4096];
int n;
while ((n = reader.read(buffer)) != -1) {
builder.append(buffer, 0, n);
}
return builder.toString();
}
finally {
reader.close();
}
}
else {
throw new UnsupportedOperationException("Only HTTP URLs are supported");
}
}
@NotNull
public Message createExecuteRequest(String code, String messageId) {
final JsonObject content = new JsonObject();
content.addProperty("code", code);
content.addProperty("silent", false);
content.add("user_variables", new JsonArray());
content.add("output_type", new JsonPrimitive(""));
content.add("user_expressions", new JsonObject());
content.addProperty("allow_stdin", false);
return createMessage("execute_request", content, messageId);
}
private Message createMessage(String messageType, JsonObject content, String messageId) {
final Header header = Header.create(messageId, "username", mySessionId, messageType);
final JsonObject parentHeader = new JsonObject();
final JsonObject metadata = new JsonObject();
return Message.create(header, parentHeader, metadata, content);
}
@SuppressWarnings("UnusedDeclaration")
private static class Kernel {
@NotNull private String id;
@NotNull
public String getId() {
return id;
}
}
@SuppressWarnings("UnusedDeclaration")
private static class Header {
private String msg_id;
private String username;
private String session;
private String msg_type;
@NotNull
public static Header create(String messageId, String username, String sessionId, String messageType) {
final Header header = new Header();
header.msg_id = messageId;
header.username = username;
header.session = sessionId;
header.msg_type = messageType;
return header;
}
public String getMessageId() {
return msg_id;
}
public String getUsername() {
return username;
}
public String getSessionId() {
return session;
}
public String getMessageType() {
return msg_type;
}
}
@SuppressWarnings("UnusedDeclaration")
private static class Message {
private Header header;
private JsonObject parent_header;
private JsonObject metadata;
private JsonObject content;
public static Message create(Header header, JsonObject parentHeader, JsonObject metadata, JsonObject content) {
final Message message = new Message();
message.header = header;
message.parent_header = parentHeader;
message.metadata = metadata;
message.content = content;
return message;
}
public Header getHeader() {
return header;
}
public JsonObject getParentHeader() {
return parent_header;
}
public JsonObject getMetadata() {
return metadata;
}
public JsonObject getContent() {
return content;
}
}
private void addCellOutput(@NotNull final PyContent content, ArrayList<IpnbOutputCell> output) {
if (content instanceof PyErrContent) {
output.add(new IpnbErrorOutputCell(((PyErrContent)content).getEvalue(),
((PyErrContent)content).getEname(), ((PyErrContent)content).getTraceback(), null));
}
else if (content instanceof PyStreamContent) {
final String data = ((PyStreamContent)content).getData();
output.add(new IpnbStreamOutputCell(data, new String[]{data}, null));
}
else if (content instanceof PyOutContent) {
final Map<String, String> data = ((PyOutContent)content).getData();
if (data.containsKey("text/latex")) {
final String text = data.get("text/latex");
final String plainText = data.get("text/plain");
output.add(new IpnbLatexOutputCell(new String[]{text}, null, new String[]{plainText}));
}
else if (data.containsKey("text/html")) {
final String html = data.get("text/html");
output.add(new IpnbHtmlOutputCell(StringUtil.splitByLinesKeepSeparators(html), StringUtil.splitByLinesKeepSeparators(html), null));
}
else if (data.containsKey("image/png")) {
final String png = data.get("image/png");
final String plainText = data.get("text/plain");
output.add(new IpnbPngOutputCell(png, StringUtil.splitByLinesKeepSeparators(plainText), null));
}
else if (data.containsKey("image/jpeg")) {
final String jpeg = data.get("image/jpeg");
final String plainText = data.get("text/plain");
output.add(new IpnbJpegOutputCell(jpeg, StringUtil.splitByLinesKeepSeparators(plainText), null));
}
else if (data.containsKey("image/svg")) {
final String svg = data.get("image/svg");
final String plainText = data.get("text/plain");
output.add(new IpnbSvgOutputCell(StringUtil.splitByLinesKeepSeparators(svg), StringUtil.splitByLinesKeepSeparators(plainText), null));
}
else {
for (Map.Entry<String, String> entry : data.entrySet()) {
output.add(new IpnbOutOutputCell(new String[]{entry.getValue()}, null));
}
}
}
}
private interface PyContent {}
@SuppressWarnings("UnusedDeclaration")
private static class PyOutContent implements PyContent {
private int execution_count;
private HashMap<String, String> data;
private JsonObject metadata;
public int getExecutionCount() {
return execution_count;
}
public Map<String, String> getData() {
return data;
}
public JsonObject getMetadata() {
return metadata;
}
}
@SuppressWarnings("UnusedDeclaration")
private static class PyErrContent implements PyContent {
private String ename;
private String evalue;
private String[] traceback;
public String getEname() {
return ename;
}
public String getEvalue() {
return evalue;
}
public String[] getTraceback() {
return traceback;
}
}
@SuppressWarnings("UnusedDeclaration")
private static class PyStreamContent implements PyContent {
private String data;
private String name;
public String getData() {
return data;
}
public String getName() {
return name;
}
}
@SuppressWarnings("UnusedDeclaration")
private static class PyStatusContent {
private String execution_state;
public String getExecutionState() {
return execution_state;
}
}
private class Draft17WithOrigin extends Draft_17 {
@Override
public Draft copyInstance() {
return new Draft17WithOrigin();
}
@NotNull
@Override
public ClientHandshakeBuilder postProcessHandshakeRequestAsClient(@NotNull ClientHandshakeBuilder request) {
super.postProcessHandshakeRequestAsClient(request);
request.put("Origin", myURI.toString());
return request;
}
}
}
@@ -0,0 +1,20 @@
package org.jetbrains.plugins.ipnb.protocol;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.ipnb.format.cells.output.IpnbOutputCell;
import java.util.List;
/**
* TODO: Expose execution counter via API
*
* @author vlan
*/
public interface IpnbConnectionListener {
void onOpen(@NotNull IpnbConnection connection);
void onOutput(@NotNull IpnbConnection connection,
@NotNull String parentMessageId,
@NotNull List<IpnbOutputCell> outputs,
@Nullable Integer execCount);
}
@@ -0,0 +1,23 @@
package org.jetbrains.plugins.ipnb.protocol;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.ipnb.format.cells.output.IpnbOutputCell;
import java.util.List;
/**
* @author vlan
*/
public class IpnbConnectionListenerBase implements IpnbConnectionListener {
@Override
public void onOpen(@NotNull IpnbConnection connection) {
}
@Override
public void onOutput(@NotNull IpnbConnection connection,
@NotNull String parentMessageId,
@NotNull List<IpnbOutputCell> outputs,
@Nullable Integer execCount) {
}
}
@@ -0,0 +1,17 @@
package org.jetbrains.plugins.ipnb.psi;
import com.intellij.lang.Language;
import com.jetbrains.python.psi.PyFileElementType;
import org.jetbrains.annotations.NotNull;
public class IpnbPyFileElementType extends PyFileElementType {
public IpnbPyFileElementType(Language language) {
super(language);
}
@NotNull
@Override
public String getExternalId() {
return "IpnbFile.Python";
}
}
@@ -0,0 +1,30 @@
package org.jetbrains.plugins.ipnb.psi;
import com.jetbrains.python.PythonFileType;
import org.jetbrains.annotations.NotNull;
public class IpnbPyFileType extends PythonFileType {
public static PythonFileType INSTANCE = new IpnbPyFileType();
protected IpnbPyFileType() {
super(new IpnbPyLanguageDialect());
}
@NotNull
@Override
public String getName() {
return "Ipnb Python";
}
@NotNull
@Override
public String getDescription() {
return "Ipnb Python";
}
@NotNull
@Override
public String getDefaultExtension() {
return "ipnb_py";
}
}
@@ -0,0 +1,76 @@
package org.jetbrains.plugins.ipnb.psi;
import com.intellij.openapi.project.Project;
import com.intellij.psi.FileViewProvider;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiManager;
import com.intellij.psi.SingleRootFileViewProvider;
import com.intellij.psi.impl.PsiManagerEx;
import com.intellij.psi.impl.file.impl.FileManager;
import com.intellij.psi.impl.source.tree.FileElement;
import com.intellij.testFramework.LightVirtualFile;
import com.jetbrains.python.psi.impl.PyFileImpl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.ipnb.editor.panels.IpnbFilePanel;
import org.jetbrains.plugins.ipnb.editor.panels.code.IpnbCodeSourcePanel;
public class IpnbPyFragment extends PyFileImpl {
private PsiElement myContext;
private boolean myPhysical;
private final IpnbFilePanel myFilePanel;
private final IpnbCodeSourcePanel myCodeSourcePanel;
private FileViewProvider myViewProvider;
public IpnbPyFragment(Project project, CharSequence text, boolean isPhysical, IpnbCodeSourcePanel codeSourcePanel) {
super(((PsiManagerEx)PsiManager.getInstance(project)).getFileManager().createFileViewProvider(
new LightVirtualFile("code.py", IpnbPyLanguageDialect.getInstance(), text), isPhysical)
);
myPhysical = isPhysical;
myCodeSourcePanel = codeSourcePanel;
myFilePanel = codeSourcePanel.getIpnbCodePanel().getFileEditor().getIpnbFilePanel();
((SingleRootFileViewProvider)getViewProvider()).forceCachedPsi(this);
}
public IpnbCodeSourcePanel getCodeSourcePanel() {
return myCodeSourcePanel;
}
protected IpnbPyFragment clone() {
final IpnbPyFragment clone = (IpnbPyFragment)cloneImpl((FileElement)calcTreeElement().clone());
clone.myPhysical = false;
clone.myOriginalFile = this;
FileManager fileManager = ((PsiManagerEx)getManager()).getFileManager();
SingleRootFileViewProvider cloneViewProvider = (SingleRootFileViewProvider)fileManager.createFileViewProvider(new LightVirtualFile(getName(), getLanguage(), getText()), false);
cloneViewProvider.forceCachedPsi(clone);
clone.myViewProvider = cloneViewProvider;
return clone;
}
public PsiElement getContext() {
return myContext;
}
@NotNull
public FileViewProvider getViewProvider() {
if(myViewProvider != null) return myViewProvider;
return super.getViewProvider();
}
public boolean isValid() {
if (!super.isValid()) return false;
if (myContext != null && !myContext.isValid()) return false;
return true;
}
public boolean isPhysical() {
return myPhysical;
}
public void setContext(PsiElement context) {
myContext = context;
}
public IpnbFilePanel getFilePanel() {
return myFilePanel;
}
}
@@ -0,0 +1,15 @@
package org.jetbrains.plugins.ipnb.psi;
import com.intellij.lang.InjectableLanguage;
import com.intellij.lang.Language;
import com.jetbrains.python.PythonLanguage;
public class IpnbPyLanguageDialect extends Language implements InjectableLanguage {
public static IpnbPyLanguageDialect getInstance() {
return (IpnbPyLanguageDialect)IpnbPyFileType.INSTANCE.getLanguage();
}
protected IpnbPyLanguageDialect() {
super(PythonLanguage.getInstance(), "IpnbPython");
}
}
@@ -0,0 +1,14 @@
package org.jetbrains.plugins.ipnb.psi;
import com.intellij.lang.PsiBuilder;
import com.jetbrains.python.parsing.ParsingContext;
import com.jetbrains.python.parsing.PyParser;
import com.jetbrains.python.parsing.StatementParsing;
import com.jetbrains.python.psi.LanguageLevel;
public class IpnbPyParser extends PyParser {
@Override
protected ParsingContext createParsingContext(PsiBuilder builder, LanguageLevel languageLevel, StatementParsing.FUTURE futureFlag) {
return new IpnbPyParsingContext(builder, languageLevel, futureFlag);
}
}
@@ -0,0 +1,30 @@
package org.jetbrains.plugins.ipnb.psi;
import com.intellij.lang.PsiParser;
import com.intellij.lexer.Lexer;
import com.intellij.openapi.project.Project;
import com.intellij.psi.tree.IFileElementType;
import com.jetbrains.python.PythonParserDefinition;
import com.jetbrains.python.lexer.PythonIndentingLexer;
import org.jetbrains.annotations.NotNull;
public class IpnbPyParserDefinition extends PythonParserDefinition {
public static final IFileElementType IPNB_PYTHON_FILE = new IpnbPyFileElementType(IpnbPyLanguageDialect.getInstance());
@NotNull
public Lexer createLexer(Project project) {
return new PythonIndentingLexer();
}
@NotNull
@Override
public PsiParser createParser(Project project) {
return new IpnbPyParser();
}
@Override
public IFileElementType getFileNodeType() {
return IPNB_PYTHON_FILE;
}
}
@@ -0,0 +1,71 @@
package org.jetbrains.plugins.ipnb.psi;
import com.intellij.lang.PsiBuilder;
import com.intellij.psi.tree.IElementType;
import com.jetbrains.python.PyTokenTypes;
import com.jetbrains.python.parsing.ExpressionParsing;
import com.jetbrains.python.parsing.ParsingContext;
import com.jetbrains.python.parsing.StatementParsing;
import com.jetbrains.python.psi.LanguageLevel;
import org.jetbrains.annotations.Nullable;
public class IpnbPyParsingContext extends ParsingContext {
private final StatementParsing myStatementParser;
private final ExpressionParsing myExpressionParser;
public IpnbPyParsingContext(final PsiBuilder builder,
LanguageLevel languageLevel,
StatementParsing.FUTURE futureFlag) {
super(builder, languageLevel, futureFlag);
myStatementParser = new IpnbPyStatementParsing(this, futureFlag);
myExpressionParser = new IpnbPyExpressionParsing(this);
}
@Override
public ExpressionParsing getExpressionParser() {
return myExpressionParser;
}
@Override
public StatementParsing getStatementParser() {
return myStatementParser;
}
private static class IpnbPyExpressionParsing extends ExpressionParsing {
public IpnbPyExpressionParsing(ParsingContext context) {
super(context);
}
@Override
protected IElementType getReferenceType() {
return IpnbPyTokenTypes.IPNB_REFERENCE;
}
public boolean parsePrimaryExpression(boolean isTargetExpression) {
final IElementType firstToken = myBuilder.getTokenType();
if (firstToken == PyTokenTypes.IDENTIFIER) {
if (isTargetExpression) {
buildTokenElement(IpnbPyTokenTypes.IPNB_TARGET, myBuilder);
}
else {
buildTokenElement(getReferenceType(), myBuilder);
}
return true;
}
return super.parsePrimaryExpression(isTargetExpression);
}
}
private static class IpnbPyStatementParsing extends StatementParsing {
protected IpnbPyStatementParsing(ParsingContext context, @Nullable FUTURE futureFlag) {
super(context, futureFlag);
}
@Override
protected IElementType getReferenceType() {
return IpnbPyTokenTypes.IPNB_REFERENCE;
}
}
}
@@ -0,0 +1,64 @@
package org.jetbrains.plugins.ipnb.psi;
import com.intellij.lang.annotation.HighlightSeverity;
import com.intellij.openapi.editor.Editor;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
import com.intellij.psi.ResolveResult;
import com.jetbrains.python.psi.PyQualifiedExpression;
import com.jetbrains.python.psi.impl.references.PyReferenceImpl;
import com.jetbrains.python.psi.resolve.PyResolveContext;
import com.jetbrains.python.psi.resolve.PyResolveUtil;
import com.jetbrains.python.psi.resolve.RatedResolveResult;
import com.jetbrains.python.psi.resolve.ResolveProcessor;
import com.jetbrains.python.psi.types.TypeEvalContext;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.ipnb.editor.panels.IpnbEditablePanel;
import org.jetbrains.plugins.ipnb.editor.panels.IpnbFilePanel;
import org.jetbrains.plugins.ipnb.editor.panels.code.IpnbCodePanel;
import java.util.List;
public class IpnbPyReference extends PyReferenceImpl {
public IpnbPyReference(PyQualifiedExpression element, @NotNull PyResolveContext context) {
super(element, context);
}
@Override
public HighlightSeverity getUnresolvedHighlightSeverity(TypeEvalContext context) {
return HighlightSeverity.WARNING;
}
@NotNull
@Override
public ResolveResult[] multiResolve(boolean incompleteCode) {
ResolveResult[] results = super.multiResolve(incompleteCode);
if (results.length == 0) {
PsiFile file = myElement.getContainingFile();
if (file instanceof IpnbPyFragment) {
final IpnbFilePanel panel = ((IpnbPyFragment)file).getFilePanel();
final List<IpnbEditablePanel> panels = panel.getIpnbPanels();
final String referencedName = myElement.getReferencedName();
if (referencedName == null) return ResolveResult.EMPTY_ARRAY;
for (IpnbEditablePanel editablePanel : panels) {
if (!(editablePanel instanceof IpnbCodePanel)) continue;
final Editor editor = ((IpnbCodePanel)editablePanel).getEditor();
final IpnbPyFragment psiFile = (IpnbPyFragment)PsiDocumentManager.getInstance(myElement.getProject()).getPsiFile(editor.getDocument());
if (psiFile == null) continue;
ResolveProcessor processor = new ResolveProcessor(referencedName);
PyResolveUtil.scopeCrawlUp(processor, psiFile, referencedName, psiFile);
final List<RatedResolveResult> resultList = getResultsFromProcessor(referencedName, processor, psiFile, psiFile);
if (resultList.size() > 0) {
List<RatedResolveResult> ret = RatedResolveResult.sorted(resultList);
return ret.toArray(new RatedResolveResult[ret.size()]);
}
}
}
}
return results;
}
}
@@ -0,0 +1,34 @@
package org.jetbrains.plugins.ipnb.psi;
import com.intellij.lang.ASTNode;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiPolyVariantReference;
import com.intellij.psi.util.PsiTreeUtil;
import com.jetbrains.python.psi.PyFromImportStatement;
import com.jetbrains.python.psi.PyImportElement;
import com.jetbrains.python.psi.impl.PyReferenceExpressionImpl;
import com.jetbrains.python.psi.impl.references.PyImportReference;
import com.jetbrains.python.psi.impl.references.PyQualifiedReference;
import com.jetbrains.python.psi.resolve.PyResolveContext;
import org.jetbrains.annotations.NotNull;
public class IpnbPyReferenceExpression extends PyReferenceExpressionImpl {
public IpnbPyReferenceExpression(ASTNode astNode) {
super(astNode);
}
@NotNull
@Override
public PsiPolyVariantReference getReference(PyResolveContext context) {
if (isQualified()) {
return new PyQualifiedReference(this, context);
}
final PsiElement importParent = PsiTreeUtil.getParentOfType(this, PyImportElement.class, PyFromImportStatement.class);
if (importParent != null) {
return PyImportReference.forElement(this, importParent, context);
}
return new IpnbPyReference(this, context);
}
}
@@ -0,0 +1,34 @@
package org.jetbrains.plugins.ipnb.psi;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Editor;
import com.intellij.util.ui.UIUtil;
import com.jetbrains.python.psi.impl.PyTargetExpressionImpl;
import org.jetbrains.plugins.ipnb.editor.IpnbFileEditor;
import org.jetbrains.plugins.ipnb.editor.panels.IpnbFilePanel;
import org.jetbrains.plugins.ipnb.editor.panels.code.IpnbCodePanel;
import org.jetbrains.plugins.ipnb.editor.panels.code.IpnbCodeSourcePanel;
public class IpnbPyTargetExpression extends PyTargetExpressionImpl {
public IpnbPyTargetExpression(ASTNode astNode) {
super(astNode);
}
@Override
public void navigate(boolean requestFocus) {
final IpnbCodeSourcePanel sourcePanel = ((IpnbPyFragment)getContainingFile()).getCodeSourcePanel();
final Editor editor = sourcePanel.getEditor();
final IpnbCodePanel codePanel = sourcePanel.getIpnbCodePanel();
final IpnbFileEditor fileEditor = codePanel.getFileEditor();
final IpnbFilePanel filePanel = fileEditor.getIpnbFilePanel();
codePanel.setEditing(true);
filePanel.setSelectedCell(codePanel);
super.navigate(false);
UIUtil.requestFocus(editor.getContentComponent());
}
}
@@ -0,0 +1,21 @@
package org.jetbrains.plugins.ipnb.psi;
import com.intellij.psi.tree.TokenSet;
import com.jetbrains.python.PythonDialectsTokenSetContributorBase;
import org.jetbrains.annotations.NotNull;
public class IpnbPyTokenSetContributor extends PythonDialectsTokenSetContributorBase {
public static final TokenSet IPNB_REFERENCE_EXPRESSIONS = TokenSet.create(IpnbPyTokenTypes.IPNB_REFERENCE);
@NotNull
@Override
public TokenSet getExpressionTokens() {
return IPNB_REFERENCE_EXPRESSIONS;
}
@NotNull
@Override
public TokenSet getReferenceExpressionTokens() {
return IPNB_REFERENCE_EXPRESSIONS;
}
}
@@ -0,0 +1,11 @@
package org.jetbrains.plugins.ipnb.psi;
import com.jetbrains.python.psi.PyElementType;
public class IpnbPyTokenTypes {
public static final PyElementType IPNB_REFERENCE = new PyElementType("IPNB_REFERENCE", IpnbPyReferenceExpression.class);
public static final PyElementType IPNB_TARGET = new PyElementType("IPNB_TARGET", IpnbPyTargetExpression.class);
private IpnbPyTokenTypes() {
}
}
File diff suppressed because one or more lines are too long
+25
View File
@@ -0,0 +1,25 @@
{
"metadata": {
"name": "",
"signature": "sha256:0b4b631419772e40e0f4893f5a0f0fe089a39e46c8862af0256164628394302d"
},
"nbformat": 3,
"nbformat_minor": 0,
"worksheets": [
{
"cells": [
{
"cell_type": "code",
"collapsed": true,
"input": [
"e = x + 2*y"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 4
}
]
}
]
}
+21
View File
@@ -0,0 +1,21 @@
{
"metadata": {
"name": "",
"signature": "sha256:0b4b631419772e40e0f4893f5a0f0fe089a39e46c8862af0256164628394302d"
},
"nbformat": 3,
"nbformat_minor": 0,
"worksheets": [
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<img src=\"images/ipython_logo.png\">"
]
}
]
}
]
}
+34
View File
@@ -0,0 +1,34 @@
{
"metadata": {
"name": "",
"signature": "sha256:0b4b631419772e40e0f4893f5a0f0fe089a39e46c8862af0256164628394302d"
},
"nbformat": 3,
"nbformat_minor": 0,
"worksheets": [
{
"cells": [
{
"cell_type": "code",
"collapsed": false,
"input": [
"srepr(e)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 5,
"text": [
"\"Add(Symbol('x'), Mul(Integer(2), Symbol('y')))\""
]
}
],
"prompt_number": 5
}
]
}
]
}
+110
View File
@@ -0,0 +1,110 @@
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.intellij.openapi.util.text.StringUtil;
import junit.framework.TestCase;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.ipnb.format.IpnbFile;
import org.jetbrains.plugins.ipnb.format.IpnbParser;
import org.jetbrains.plugins.ipnb.format.cells.IpnbCodeCell;
import org.jetbrains.plugins.ipnb.format.cells.IpnbCell;
import org.jetbrains.plugins.ipnb.format.cells.IpnbMarkdownCell;
import org.jetbrains.plugins.ipnb.format.cells.output.IpnbOutputCell;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.List;
public class JsonParserTest extends TestCase {
public void testFile() throws IOException {
final String fileName = "testData/SymPy.ipynb";
final String fileText = getFileText(fileName);
final IpnbFile ipnbFile = IpnbParser.parseIpnbFile(fileText, fileName);
assertNotNull(ipnbFile);
assertEquals(31, ipnbFile.getCells().size());
}
public void testMarkdownCells() throws IOException {
final String fileName = "testData/SymPy.ipynb";
final String fileText = getFileText(fileName);
final IpnbFile ipnbFile = IpnbParser.parseIpnbFile(fileText,fileName);
assertNotNull(ipnbFile);
final List<IpnbCell> cells = ipnbFile.getCells();
Iterables.removeIf(cells, new Predicate<IpnbCell>() {
@Override
public boolean apply(IpnbCell cell) {
return !(cell instanceof IpnbMarkdownCell);
}
});
assertEquals(7, cells.size());
}
public void testMarkdownCell() throws IOException {
final String fileName = "testData/markdown.ipynb";
final String fileText = getFileText(fileName);
final IpnbFile ipnbFile = IpnbParser.parseIpnbFile(fileText, fileName);
assertNotNull(ipnbFile);
final List<IpnbCell> cells = ipnbFile.getCells();
assertEquals(1, cells.size());
final IpnbCell cell = cells.get(0);
assertTrue(cell instanceof IpnbMarkdownCell);
final String[] source = ((IpnbMarkdownCell)cell).getSource();
final String joined = StringUtil.join(source);
assertEquals("<img src=\"images/ipython_logo.png\">", joined);
}
public void testCodeCell() throws IOException {
final String fileName = "testData/code.ipynb";
final String fileText = getFileText(fileName);
final IpnbFile ipnbFile = IpnbParser.parseIpnbFile(fileText, fileName);
assertNotNull(ipnbFile);
final List<IpnbCell> cells = ipnbFile.getCells();
assertEquals(1, cells.size());
final IpnbCell cell = cells.get(0);
assertTrue(cell instanceof IpnbCodeCell);
final List<IpnbOutputCell> outputs = ((IpnbCodeCell)cell).getCellOutputs();
assertEquals(0, outputs.size());
final String[] source = ((IpnbCodeCell)cell).getSource();
final String joined = StringUtil.join(source);
assertEquals("e = x + 2*y", joined);
final String language = ((IpnbCodeCell)cell).getLanguage();
assertEquals("python", language);
final Integer number = ((IpnbCodeCell)cell).getPromptNumber();
assertEquals(new Integer(4), number);
}
public void testOutputs() throws IOException {
final String fileName = "testData/outputs.ipynb";
final String fileText = getFileText(fileName);
final IpnbFile ipnbFile = IpnbParser.parseIpnbFile(fileText, fileName);
assertNotNull(ipnbFile);
final List<IpnbCell> cells = ipnbFile.getCells();
assertEquals(1, cells.size());
final IpnbCell cell = cells.get(0);
assertTrue(cell instanceof IpnbCodeCell);
final List<IpnbOutputCell> outputs = ((IpnbCodeCell)cell).getCellOutputs();
assertEquals(1, outputs.size());
final IpnbOutputCell output = outputs.get(0);
final String[] text = output.getText();
final String joined = StringUtil.join(text);
assertEquals("\"Add(Symbol('x'), Mul(Integer(2), Symbol('y')))\"", joined);
}
private String getFileText(@NotNull final String fileName) throws IOException {
final BufferedReader br = new BufferedReader(new FileReader(fileName));
try {
final StringBuilder sb = new StringBuilder();
String line = br.readLine();
while (line != null) {
sb.append(line);
sb.append("\n");
line = br.readLine();
}
return sb.toString();
}
finally {
br.close();
}
}
}
@@ -0,0 +1,116 @@
import com.intellij.openapi.util.Ref;
import junit.framework.TestCase;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.ipnb.format.cells.output.IpnbOutOutputCell;
import org.jetbrains.plugins.ipnb.format.cells.output.IpnbOutputCell;
import org.jetbrains.plugins.ipnb.protocol.IpnbConnection;
import org.jetbrains.plugins.ipnb.protocol.IpnbConnectionListenerBase;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
/**
*
* * Message Spec
* http://ipython.org/ipython-doc/dev/development/messaging.html
*
* * Notebook REST API
* https://github.com/ipython/ipython/wiki/IPEP-16%3A-Notebook-multi-directory-dashboard-and-URL-mapping
*
* @author vlan
*/
public class WebSocketConnectionTest extends TestCase {
@Override
protected void setUp() throws Exception {
//WebSocketImpl.DEBUG = true;
}
public void testStartAndShutdownKernel() throws URISyntaxException, IOException, InterruptedException {
final IpnbConnection connection = new IpnbConnection(getTestServerURI(), new IpnbConnectionListenerBase() {
@Override
public void onOpen(@NotNull IpnbConnection connection) {
assertTrue(connection.getKernelId().length() > 0);
connection.shutdown();
}
});
connection.close();
}
public void testBasicWebSocket() throws IOException, URISyntaxException, InterruptedException {
final Ref<Boolean> evaluated = Ref.create(false);
final IpnbConnection connection = new IpnbConnection(getTestServerURI(), new IpnbConnectionListenerBase() {
private String myMessageId;
@Override
public void onOpen(@NotNull IpnbConnection connection) {
myMessageId = connection.execute("2 + 2");
}
@Override
public void onOutput(@NotNull IpnbConnection connection,
@NotNull String parentMessageId,
@NotNull List<IpnbOutputCell> outputs,
Integer execCount) {
if (myMessageId.equals(parentMessageId)) {
assertEquals(outputs.size(), 1);
assertEquals(outputs.get(0).getClass(), IpnbOutOutputCell.class);
final String[] text = outputs.get(0).getText();
assertNotNull(text);
assertEquals("4", text[0]);
evaluated.set(true);
connection.shutdown();
}
}
});
connection.close();
assertTrue(evaluated.get());
}
public void testCompositeInput() throws IOException, URISyntaxException, InterruptedException {
final Ref<Boolean> evaluated = Ref.create(false);
final IpnbConnection connection = new IpnbConnection(getTestServerURI(), new IpnbConnectionListenerBase() {
private String myMessageId;
@Override
public void onOpen(@NotNull IpnbConnection connection) {
myMessageId = connection.execute("def simple_crit_func(feat_sub):\n" +
"\n" +
" \"\"\" Returns sum of numerical values of an input list. \"\"\" \n" +
"\n" +
" return sum(feat_sub)\n" +
"\n" +
"simple_crit_func([1,2,4])");
}
@Override
public void onOutput(@NotNull IpnbConnection connection,
@NotNull String parentMessageId,
@NotNull List<IpnbOutputCell> outputs,
Integer execCount) {
if (myMessageId.equals(parentMessageId)) {
assertEquals(outputs.size(), 1);
assertEquals(outputs.get(0).getClass(), IpnbOutOutputCell.class);
final String[] text = outputs.get(0).getText();
assertNotNull(text);
assertEquals("7", text[0]);
evaluated.set(true);
connection.shutdown();
}
}
});
connection.close();
assertTrue(evaluated.get());
}
@NotNull
public static URI getTestServerURI() {
try {
return new URI("http://127.0.0.1:8888");
}
catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
}