├── .idea ├── .name ├── copyright │ └── profiles_settings.xml ├── scopes │ └── scope_settings.xml ├── encodings.xml ├── vcs.xml ├── misc.xml ├── modules.xml ├── libraries │ ├── Gradle__org_hamcrest_hamcrest_core_1_3.xml │ ├── Gradle__org_jetbrains_annotations_19_0_0.xml │ └── Gradle__junit_junit_4_12.xml ├── compiler.xml ├── uiDesigner.xml └── modules │ ├── PythonCellMode.test.iml │ └── PythonCellMode.main.iml ├── PythonCellMode.jar ├── images └── sdk_configuration.png ├── .gitignore ├── LICENSE.txt ├── PythonCellMode.iml ├── src ├── RunLineAction.java ├── RunSelectionAction.java ├── PythonCellRunLineMarker.java ├── RunCellMoveNextAction.java ├── RunCellAction.java ├── PythonCellLineSeparatorProvider.java ├── Preferences.java ├── CellModeConfigurable.java ├── Tmux.java ├── AbstractRunAction.java ├── PythonConsoleUtils.java └── CellModeConfigurable.form ├── META-INF └── plugin.xml └── README.md /.idea/.name: -------------------------------------------------------------------------------- 1 | PythonCellMode -------------------------------------------------------------------------------- /PythonCellMode.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/julienr/pycharm-cellmode/HEAD/PythonCellMode.jar -------------------------------------------------------------------------------- /images/sdk_configuration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/julienr/pycharm-cellmode/HEAD/images/sdk_configuration.png -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # https://intellij-support.jetbrains.com/entries/23393067 2 | .idea/workspace.xml 3 | .idea/tasks.xml 4 | .idea/dictionaries 5 | .DS_Store 6 | out/ 7 | -------------------------------------------------------------------------------- /.idea/scopes/scope_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_hamcrest_hamcrest_core_1_3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2014 Julien Rebetez 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_jetbrains_annotations_19_0_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /PythonCellMode.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__junit_junit_4_12.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/RunLineAction.java: -------------------------------------------------------------------------------- 1 | import com.intellij.openapi.editor.Document; 2 | import com.intellij.openapi.editor.Editor; 3 | 4 | // Runs the current line under the caret 5 | public class RunLineAction extends AbstractRunAction { 6 | static private String myText = "Run Line"; 7 | 8 | RunLineAction() { 9 | super(myText); 10 | } 11 | 12 | @Override 13 | protected Block findBlock(Editor editor) { 14 | Document document = editor.getDocument(); 15 | int docCaretOffset = editor.getCaretModel().getOffset(); 16 | int caretLineNumber = document.getLineNumber(docCaretOffset); 17 | 18 | int start = document.getLineStartOffset(caretLineNumber); 19 | int end = document.getLineEndOffset(caretLineNumber); 20 | CharSequence blockText = document.getCharsSequence().subSequence(start, end); 21 | return new Block(blockText.toString(), caretLineNumber, caretLineNumber); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/RunSelectionAction.java: -------------------------------------------------------------------------------- 1 | import com.intellij.openapi.editor.Document; 2 | import com.intellij.openapi.editor.Editor; 3 | import com.intellij.openapi.editor.SelectionModel; 4 | import org.jetbrains.annotations.NotNull; 5 | import org.jetbrains.annotations.Nullable; 6 | 7 | // Run currently selected text 8 | public class RunSelectionAction extends AbstractRunAction { 9 | static private String myText = "Run Selection"; 10 | 11 | RunSelectionAction() { 12 | super(myText); 13 | } 14 | 15 | protected Block findBlock(Editor editor) { 16 | if (editor.getSelectionModel().hasSelection()) { 17 | Document doc = editor.getDocument(); 18 | SelectionModel model = editor.getSelectionModel(); 19 | String selectedText = model.getSelectedText(); 20 | int selectStartLine = doc.getLineNumber(model.getSelectionStart()); 21 | int selectEndLine = doc.getLineNumber(model.getSelectionEnd()); 22 | return new Block(selectedText, selectStartLine, selectEndLine); 23 | } 24 | else { 25 | return null; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/PythonCellRunLineMarker.java: -------------------------------------------------------------------------------- 1 | import com.intellij.execution.lineMarker.RunLineMarkerContributor; 2 | import com.intellij.icons.AllIcons; 3 | import com.intellij.openapi.actionSystem.ActionManager; 4 | import com.intellij.openapi.actionSystem.AnAction; 5 | import com.intellij.openapi.editor.Document; 6 | import com.intellij.psi.*; 7 | import org.jetbrains.annotations.NotNull; 8 | import org.jetbrains.annotations.Nullable; 9 | 10 | import java.util.regex.Pattern; 11 | 12 | public class PythonCellRunLineMarker extends RunLineMarkerContributor { 13 | 14 | protected final Preferences prefs = new Preferences(); 15 | 16 | @Nullable 17 | @Override 18 | public Info getInfo(@NotNull PsiElement element) { 19 | if (element instanceof PsiComment) { 20 | PsiComment comment = (PsiComment) element; 21 | String value = comment.getText(); 22 | Pattern pattern = Pattern.compile(prefs.getDelimiterRegexp()); 23 | int lineNumber = PythonCellLineSeparatorProvider.getLineNumber(pattern, comment); 24 | if (lineNumber >= 0) { 25 | return new Info( 26 | AllIcons.RunConfigurations.TestState.Run, 27 | getActions(lineNumber), 28 | (PsiElement e) -> "Run Cell"); 29 | } 30 | } 31 | return null; 32 | } 33 | 34 | private AnAction[] getActions(int lineNumber) { 35 | return new RunCellAction[]{new RunCellAction(lineNumber), new RunCellMoveNextAction(lineNumber)}; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/RunCellMoveNextAction.java: -------------------------------------------------------------------------------- 1 | import com.intellij.openapi.command.WriteCommandAction; 2 | import com.intellij.openapi.editor.Document; 3 | import com.intellij.openapi.editor.Editor; 4 | import com.intellij.openapi.editor.ScrollType; 5 | 6 | // Like RunCellAction, but moves to next cell afterwards 7 | public class RunCellMoveNextAction extends RunCellAction { 8 | static private String myText = "Run Cell And Move Next"; 9 | 10 | public RunCellMoveNextAction() { 11 | super(myText, -1); 12 | } 13 | 14 | public RunCellMoveNextAction(int lineNumber) { 15 | super(myText, lineNumber); 16 | } 17 | 18 | @Override 19 | protected void postExecuteHook(Editor editor, Block block) { 20 | moveCaretToLineStart(editor, block.lineEnd + 1, prefs.getDelimiterInsert()); 21 | } 22 | 23 | private static void moveCaretToLineStart(Editor editor, int line, String delimiterInsert) { 24 | if (line < 0) 25 | return; 26 | Document doc = editor.getDocument(); 27 | if (line != 0 && line < doc.getLineCount()) { 28 | int newOffset = doc.getLineStartOffset(line); 29 | editor.getCaretModel().moveToOffset(newOffset); 30 | editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); 31 | } else { 32 | WriteCommandAction.runWriteCommandAction(editor.getProject(), () -> { 33 | int offset = doc.getLineEndOffset(doc.getLineCount() - 1); 34 | doc.insertString(offset, "\n" + delimiterInsert + "\n\n"); 35 | 36 | int newOffset = doc.getLineStartOffset(doc.getLineCount() - 2); 37 | editor.getCaretModel().moveToOffset(newOffset); 38 | }); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/RunCellAction.java: -------------------------------------------------------------------------------- 1 | import com.intellij.openapi.editor.Document; 2 | import com.intellij.openapi.editor.Editor; 3 | 4 | 5 | // grepcode.com is a good source to search the intellij API 6 | // Document API : 7 | // http://grepcode.com/file/repository.grepcode.com/java/ext/com.jetbrains/intellij-idea/13.0.0/com/intellij/openapi/editor/Document.java#Document.getText%28com.intellij.openapi.util.TextRange%29 8 | 9 | // Heavily inspired by the PyExecuteSelectionAction : 10 | // https://github.com/JetBrains/intellij-community/blob/135/python/src/com/jetbrains/python/actions/ExecuteInConsoleAction.java 11 | 12 | // Note that the console function will change in the next pycharm release (tagged 139 on git) 13 | public class RunCellAction extends AbstractRunAction { 14 | static private String myText = "Run Cell"; 15 | private int lineNumber; 16 | 17 | public RunCellAction(String text, int lineNumber) { 18 | super(text); 19 | this.lineNumber = lineNumber; 20 | } 21 | 22 | public RunCellAction() { 23 | super(myText); 24 | this.lineNumber = -1; 25 | } 26 | 27 | public RunCellAction(int lineNumber) { 28 | super(myText); 29 | this.lineNumber = lineNumber; 30 | } 31 | 32 | /** 33 | * Finds the current python block (delimited by ##) in which the caret is. 34 | * @param editor The editor in which to find the block 35 | * @return A Block containing the block text or null if no block was found 36 | */ 37 | protected Block findBlock(Editor editor) { 38 | Document document = editor.getDocument(); 39 | int caretLineNumber = lineNumber; 40 | if (lineNumber == -1) { 41 | int docCaretOffset = editor.getCaretModel().getOffset(); 42 | caretLineNumber = document.getLineNumber(docCaretOffset); 43 | } 44 | 45 | int lineUp = searchForDelimiter(document, caretLineNumber, -1); 46 | int lineDown = searchForDelimiter(document, caretLineNumber + 1, 1); 47 | 48 | //System.out.println("lineUp : " + lineUp + ", lineDown : " + lineDown); 49 | int start, end; 50 | if (lineUp == -1) { 51 | // from top 52 | start = 0; 53 | } else { 54 | // from '##' 55 | start = document.getLineStartOffset(lineUp + 1); 56 | } 57 | 58 | if (lineDown == -1) { 59 | // to bottom 60 | lineDown = document.getLineCount(); 61 | } 62 | if (lineDown == 0) { 63 | end = 0; 64 | } else { 65 | // to '##' 66 | end = document.getLineEndOffset(lineDown - 1); 67 | } 68 | 69 | if (end - start > 0) { 70 | CharSequence blockText = document.getCharsSequence().subSequence(start, end); 71 | //System.out.println("blockText : " + blockText); 72 | return new Block(blockText.toString(), lineUp, lineDown); 73 | } 74 | 75 | return null; 76 | } 77 | 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/PythonCellLineSeparatorProvider.java: -------------------------------------------------------------------------------- 1 | import com.intellij.codeHighlighting.Pass; 2 | import com.intellij.codeInsight.daemon.*; 3 | import com.intellij.openapi.editor.Document; 4 | import com.intellij.openapi.editor.colors.CodeInsightColors; 5 | import com.intellij.openapi.editor.colors.EditorColorsManager; 6 | import com.intellij.openapi.editor.markup.GutterIconRenderer; 7 | import com.intellij.openapi.editor.markup.SeparatorPlacement; 8 | import com.intellij.psi.*; 9 | import com.intellij.psi.util.PsiTreeUtil; 10 | import org.jetbrains.annotations.NotNull; 11 | 12 | import java.util.Collection; 13 | import java.util.List; 14 | import java.util.regex.Pattern; 15 | 16 | public class PythonCellLineSeparatorProvider implements LineMarkerProvider { 17 | private final EditorColorsManager colorsManager; 18 | 19 | protected final Preferences prefs = new Preferences(); 20 | 21 | private final DaemonCodeAnalyzerSettings daemonSettings; 22 | 23 | public PythonCellLineSeparatorProvider() { 24 | this.colorsManager = EditorColorsManager.getInstance(); 25 | this.daemonSettings = DaemonCodeAnalyzerSettings.getInstance(); 26 | } 27 | 28 | @Override 29 | public LineMarkerInfo getLineMarkerInfo(@NotNull PsiElement element) { 30 | if (element instanceof PsiComment) { 31 | Pattern pattern = Pattern.compile(prefs.getDelimiterRegexp()); 32 | if (getLineNumber(pattern, element) >= 0) { 33 | return createLineSeparatorByElement(element); 34 | } 35 | } 36 | return null; 37 | } 38 | 39 | @Override 40 | public void collectSlowLineMarkers(@NotNull List list, @NotNull Collection> collection) { 41 | } 42 | 43 | public static int getLineNumber(Pattern pattern, PsiElement element) { 44 | Document document = PsiDocumentManager.getInstance(element.getProject()).getDocument(element.getContainingFile()); 45 | if (document != null) { 46 | int lineNumber = document.getLineNumber(element.getTextRange().getStartOffset()); 47 | 48 | int start = document.getLineStartOffset(lineNumber); 49 | int end = document.getLineEndOffset(lineNumber); 50 | CharSequence text = document.getCharsSequence().subSequence(start, end); 51 | if (pattern.matcher(text).matches()) { 52 | return lineNumber; 53 | } 54 | } 55 | return -1; 56 | } 57 | 58 | private LineMarkerInfo createLineSeparatorByElement(PsiElement element) { 59 | PsiElement anchor = PsiTreeUtil.getDeepestFirst(element); 60 | 61 | LineMarkerInfo lineMarkerInfo = new LineMarkerInfo(anchor, anchor.getTextRange()); 62 | lineMarkerInfo.separatorColor = colorsManager.getGlobalScheme().getColor(CodeInsightColors.METHOD_SEPARATORS_COLOR); 63 | lineMarkerInfo.separatorPlacement = SeparatorPlacement.TOP; 64 | return lineMarkerInfo; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Preferences.java: -------------------------------------------------------------------------------- 1 | import com.intellij.ide.util.PropertiesComponent; 2 | 3 | import java.util.regex.Pattern; 4 | import java.util.regex.PatternSyntaxException; 5 | 6 | public class Preferences { 7 | private final static String PREFS_PREFIX = "net.fhtagn.pycharm.cellmode"; 8 | 9 | private final static String PREF_TARGET = PREFS_PREFIX + "target"; 10 | public final static int TARGET_INTERNAL_CONSOLE = 0; 11 | public final static int TARGET_TMUX = 1; 12 | 13 | private final static String PREF_TMUX_TARGET = PREFS_PREFIX + "tmux_target"; 14 | private final static String PREF_TMUX_EXECPATH = PREFS_PREFIX + "tmux_path"; 15 | private final static String PREF_TMUX_TEMPFILE = PREFS_PREFIX + "tmux_tempfile"; 16 | private final static String PREF_DELIMITER_REGEXP = PREFS_PREFIX + "delimiter regexp"; 17 | private final static String PREF_DELIMITER_INSERT = PREFS_PREFIX + "delimiter insert"; 18 | 19 | private final PropertiesComponent props; 20 | 21 | public Preferences() { 22 | // http://confluence.jetbrains.com/display/IDEADEV/Persisting+State+of+Components 23 | // TODO : Should make the config per-project. But how do we get current project from here ? 24 | this.props = PropertiesComponent.getInstance(); 25 | //props.setValue("") 26 | } 27 | 28 | public String getDelimiterRegexp() { 29 | return props.getValue(PREF_DELIMITER_REGEXP, "^\\s*##.*"); 30 | } 31 | 32 | public void setDelimiterRegexp(String regexp) { 33 | try { 34 | Pattern.compile(regexp); 35 | props.setValue(PREF_DELIMITER_REGEXP, regexp); 36 | } catch (PatternSyntaxException ignored) { 37 | } 38 | } 39 | 40 | public String getDelimiterInsert() { 41 | return props.getValue(PREF_DELIMITER_INSERT, "##"); 42 | } 43 | 44 | public void setDelimiterInsert(String regexp) { 45 | props.setValue(PREF_DELIMITER_INSERT, regexp); 46 | } 47 | 48 | public void setTargetConsole(int target) { 49 | if (target > 1) { 50 | // TODO: Should we throw an exception ? Not sure it's a good idea to crash the whole editor 51 | System.err.println("Invalid target : " + target); 52 | } 53 | props.setValue(PREF_TARGET, String.valueOf(target)); 54 | } 55 | 56 | public int getTargetConsole() { 57 | return props.getInt(PREF_TARGET, TARGET_INTERNAL_CONSOLE); 58 | } 59 | 60 | public void setTmuxTarget(String target) { 61 | props.setValue(PREF_TMUX_TARGET, target); 62 | } 63 | 64 | public String getTmuxTarget() { 65 | return props.getValue(PREF_TMUX_TARGET, "$ipython:ipython.0"); 66 | } 67 | 68 | public void setTmuxExecutable(String execPath) { 69 | props.setValue(PREF_TMUX_EXECPATH, execPath); 70 | } 71 | 72 | public String getTmuxExecutable() { 73 | return props.getValue(PREF_TMUX_EXECPATH, "/usr/bin/tmux"); 74 | } 75 | 76 | public void setTmuxTempFilename(String fpath) { 77 | props.setValue(PREF_TMUX_TEMPFILE, fpath); 78 | } 79 | 80 | public String getTmuxTempFilename() { 81 | return props.getValue(PREF_TMUX_TEMPFILE, "/tmp/pycharm.cellmode.tmux"); 82 | } 83 | 84 | 85 | } 86 | -------------------------------------------------------------------------------- /src/CellModeConfigurable.java: -------------------------------------------------------------------------------- 1 | import com.intellij.openapi.options.Configurable; 2 | import com.intellij.openapi.options.ConfigurationException; 3 | import com.intellij.ui.IdeBorderFactory; 4 | 5 | import javax.swing.*; 6 | 7 | public class CellModeConfigurable implements Configurable { 8 | private JPanel panel1; 9 | private JRadioButton sendInternal; 10 | private JRadioButton sendTmux; 11 | private JTextField ipythonSessionName; 12 | private JPanel ipythonOptionsPanel; 13 | private JPanel targetPanel; 14 | private JTextField tmuxExecPath; 15 | private JTextField tmuxTempFilename; 16 | private JPanel consolePanel; 17 | private JTextField delimiterRegexp; 18 | private JPanel delimiterPanel; 19 | private JPanel keymapPanel; 20 | private JTextField delimiterInsert; 21 | 22 | private final Preferences prefs; 23 | 24 | public CellModeConfigurable() { 25 | prefs = new Preferences(); 26 | } 27 | 28 | @Override 29 | public String getDisplayName() { 30 | return "Python Cell Mode"; 31 | } 32 | 33 | @Override 34 | public String getHelpTopic() { 35 | return null; 36 | } 37 | 38 | @Override 39 | public JComponent createComponent() { 40 | delimiterRegexp.setText(prefs.getDelimiterRegexp()); 41 | delimiterInsert.setText(prefs.getDelimiterInsert()); 42 | consolePanel.setBorder(IdeBorderFactory.createTitledBorder("Target Python Console", false)); 43 | ipythonOptionsPanel.setBorder(IdeBorderFactory.createTitledBorder("tmux Options", false)); 44 | keymapPanel.setBorder(IdeBorderFactory.createTitledBorder("Keyboard shortcuts", false)); 45 | delimiterPanel.setBorder(IdeBorderFactory.createTitledBorder("Cell delimiter", false)); 46 | ipythonSessionName.setText(prefs.getTmuxTarget()); 47 | tmuxExecPath.setText(prefs.getTmuxExecutable()); 48 | tmuxTempFilename.setText(prefs.getTmuxTempFilename()); 49 | sendInternal.setSelected(prefs.getTargetConsole() == Preferences.TARGET_INTERNAL_CONSOLE); 50 | sendTmux.setSelected(prefs.getTargetConsole() == Preferences.TARGET_TMUX); 51 | return (JComponent)targetPanel; 52 | } 53 | 54 | @Override 55 | public boolean isModified() { 56 | return true; 57 | } 58 | 59 | @Override 60 | public void apply() throws ConfigurationException { 61 | prefs.setTmuxTarget(ipythonSessionName.getText()); 62 | prefs.setTmuxExecutable(tmuxExecPath.getText()); 63 | prefs.setTmuxTempFilename(tmuxTempFilename.getText()); 64 | if (sendInternal.isSelected()) { 65 | prefs.setTargetConsole(Preferences.TARGET_INTERNAL_CONSOLE); 66 | } else { 67 | prefs.setTargetConsole(Preferences.TARGET_TMUX); 68 | } 69 | prefs.setDelimiterRegexp(delimiterRegexp.getText()); 70 | prefs.setDelimiterInsert(delimiterInsert.getText()); 71 | //System.out.println("target console : " + prefs.getTargetConsole()); 72 | //System.out.println("tmux exec : " + prefs.getTmuxExecutable() + ", session : " + prefs.getTmuxTarget()); 73 | } 74 | 75 | @Override 76 | public void reset() { 77 | 78 | } 79 | 80 | @Override 81 | public void disposeUIResources() { 82 | 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /META-INF/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | net.fhtagn.pycharm.cellmode 3 | PyCharm cell mode 4 | 1.3.1 5 | Julien Rebetez, clouds56 6 | 7 | ## marks to the python console. 10 | ]]> 11 | 12 | 14 | - Remove deprecated API usage. Target 203.3645.34
15 |
16 | #1.3.0
17 | - Fix issues with Pycharm 2021
18 |
19 | # 1.2.1
20 | - Initial version. 21 | ]]> 22 |
23 | 24 | 25 | 26 | 27 | 29 | 32 | com.intellij.modules.python 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 55 | 56 | 57 | 58 | 60 | 61 | 62 | 64 | 65 | 66 | 68 | 69 | 70 | 71 | 72 |
-------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deprecation warning 2 | 3 | This plugin is not actively developed anymore and may not work correctly in the latest version of PyCharm. 4 | 5 | If anyone wants to take over as a maintainer, I'm happy to archive this repository and redirect to a maintained fork. 6 | 7 | # Python cell mode for PyCharm 8 | This plugin provides actions to allow executing Python code using "cells" in PyCharm, much like [Spyder](https://docs.spyder-ide.org/current/editor.html#defining-code-cells). 9 | 10 | A "code cell" is a block of lines, typically delimited by `##`, for example: 11 | 12 | ## 13 | print 'foo' 14 | if True: 15 | print 'bar' 16 | ## 17 | 18 | The plugin options allow you to specify your own regular expression to delimits code cells. 19 | 20 | This plugin provides 3 actions under the Code menu, and you can assign keyboard shortcuts to each: 21 | 22 | - Run Cell: run the current cell 23 | - Run Cell and Move Next: run the current cell and advance cursor to next cell 24 | - Run Line under the caret 25 | 26 | When using `Run Cell and Move Next` and there is no next cell, a delimiter (which can be specified in the options, but which is unrelated to the above described regular expression) will be inserted in the source code. 27 | 28 | The cell can be sent to either : 29 | 30 | - the internal IPython console 31 | - an external IPython kernel running in a tmux 32 | 33 | The second option allows you to have a working interactive matplotlib in an external IPython process. 34 | 35 | Check the "Python Cell Mode" settings in the preferences to switch between the two modes. 36 | 37 | This plugin is similar to https://github.com/julienr/vim-cellmode 38 | 39 | ## Installation 40 | 41 | Install from [jetbrains plugin repository](https://plugins.jetbrains.com/plugin/7858) 42 | 43 | Alternatively, you can install directly from the jar : 44 | 45 | 1. Download [PythonCellMode.jar](https://github.com/julienr/pycharm-cellmode/blob/master/PythonCellMode.jar) 46 | 2. In PyCharm, go to "Preferences", search for "plugin". Click on "Install from disk" and choose the downloaded jar 47 | 3. Restart PyCharm and use the new actions in the "Code" menu 48 | 4. (optional) Configure keyboard shortcuts by searching for "Cell" in your keymap 49 | 50 | ## Developing the plugin 51 | 52 | For now, here are some instructions from memory that may be helpful: 53 | (copied from https://github.com/Khan/ka-pycharm-plugin ) 54 | 55 | 1. Install IntelliJ IDEA Community Edition. 56 | 2. Open the repo as an IntelliJ project. 57 | 3. Choose your PyCharm installation as the plugin development SDK. 58 | 4. Make a run configuration from within IntelliJ and run it. If things work, it will launch a fresh PyCharm instance 59 | with the plugin installed, which you can use for testing. 60 | 61 | ### Dependencies on com.jetbrains.python.console 62 | 63 | After just loading the plugin in Intellij, you might have missing dependencies on `com.jetbrains.python.console`. 64 | 65 | As explaind on [this page](https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html?from=DevkitPluginXmlInspection#dependency-declaration-in-pluginxml), what you need 66 | to do is to manually add 'python-ce' to the classpath of the selected SDK (Pycharm community edition). 67 | 68 | To do so, right click on 'PythonCellMode' -> Open Module Settings and then your SDK should look like this: 69 | 70 | ![SDK configuration](/images/sdk_configuration.png?raw=true) 71 | 72 | ### Relevant plugin dev links 73 | 74 | http://bjorn.tipling.com/how-to-make-an-intellij-idea-plugin-in-30-minutes 75 | 76 | http://confluence.jetbrains.com/display/IDEADEV/Plugin+Compatibility+with+IntelliJ+Platform+Products 77 | 78 | https://github.com/JetBrains/intellij-community/blob/1d171c3a1a5fafb82c9a10f8f7b2acd616254f38/python/src/com/jetbrains/python/actions/PyExecuteSelectionAction.java 79 | 80 | https://confluence.jetbrains.com/display/IDEADEV/PluginDevelopment 81 | -------------------------------------------------------------------------------- /src/Tmux.java: -------------------------------------------------------------------------------- 1 | import com.intellij.openapi.ui.Messages; 2 | import org.apache.commons.lang.StringUtils; 3 | 4 | import java.io.*; 5 | 6 | public class Tmux { 7 | private static Object monitor = new Object(); 8 | 9 | public static void executeInTmux(Preferences prefs, String codeText) { 10 | String target = "\"" + prefs.getTmuxTarget() + "\""; 11 | String tmuxExec = prefs.getTmuxExecutable(); 12 | String fname = prefs.getTmuxTempFilename(); 13 | try { 14 | File temp = new File(fname); 15 | 16 | // We don't want to have multiple command interleaved 17 | synchronized(monitor) { 18 | // Add end-of-line 19 | writeToFile(temp, codeText + "\n"); 20 | // TODO: Check if tmux target exists 21 | 22 | // Use the ipython %load magic 23 | runCommand(tmuxExec, "set-buffer", "\"%load -y " + temp.getAbsolutePath() + "\"\n"); 24 | runCommand(tmuxExec, "paste-buffer", "-t " + target); 25 | // Simulate double enter to scroll through and run loaded code 26 | runCommand(tmuxExec, "send-keys", "-t " + target, "Enter"); 27 | runCommand(tmuxExec, "send-keys", "-t " + target, "Enter"); 28 | } 29 | 30 | } catch (IOException e) { 31 | e.printStackTrace(); 32 | } 33 | } 34 | 35 | private static void writeToFile(File tempFile, String codeText) throws IOException { 36 | BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile)); 37 | writer.write(codeText); 38 | // If the file is empty, it seems like tmux load-buffer keep the current 39 | // buffer and this cause the last command to be repeated. We do not want that 40 | // to happen, so add a dummy string 41 | writer.write(" "); 42 | writer.close(); 43 | } 44 | 45 | private static String readFully(InputStream stream) { 46 | StringBuilder sb = new StringBuilder(); 47 | try { 48 | byte[] buffer = new byte[1024]; 49 | while (true) { 50 | int numRead = stream.read(buffer); 51 | if (numRead == -1) { 52 | break; 53 | } 54 | for (int i = 0; i < numRead; ++i) { 55 | sb.append((char)buffer[i]); 56 | } 57 | } 58 | return sb.toString(); 59 | } catch (IOException e) { 60 | e.printStackTrace(); 61 | return sb.toString(); 62 | } 63 | } 64 | 65 | private static void runCommand(String... args) throws IOException { 66 | // See http://tomaszdziurko.pl/2011/09/developing-plugin-intellij-idea-some-tips-and-links/ 67 | // StatusBar statusBar = WindowManager.getInstance() 68 | // .getStatusBar(DataKeys.PROJECT.getData(actionEvent.getDataContext())); 69 | // JBPopupFactory.getInstance() 70 | // .createHtmlTextBalloonBuilder(htmlText, messageType, null) 71 | // .setFadeoutTime(7500) 72 | // .createBalloon() 73 | // .show(RelativePoint.getCenterOf(statusBar.getComponent()), 74 | // Balloon.Position.atRight); 75 | final boolean isUnix = !System.getProperty("os.name").startsWith("Windows"); 76 | 77 | ProcessBuilder pb; 78 | if (isUnix) { 79 | // TODO: For some strange reason, direct execution (without /bin/sh) fails on the -t $sess:win.0 . It seems 80 | // that the ".0" at the end gets truncated for whatever reason and tmux doesn't find the session... 81 | // encoding or escaping issue ? 82 | // So for now, do everything through /bin/sh 83 | final String command = StringUtils.join(args, " "); 84 | pb = new ProcessBuilder("/bin/sh", "-c", command); 85 | } else { 86 | pb = new ProcessBuilder(args); 87 | } 88 | 89 | pb.redirectErrorStream(true); 90 | Process p = pb.start(); 91 | try { 92 | p.waitFor(); 93 | if (p.exitValue() != 0) { 94 | String msg = "Error executing " + StringUtils.join(args, " ") + "\n" 95 | + "Error : " + readFully(p.getInputStream()); 96 | Messages.showErrorDialog(msg, "Python Cell Mode Error"); 97 | } 98 | } catch (InterruptedException e) { 99 | e.printStackTrace(); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/AbstractRunAction.java: -------------------------------------------------------------------------------- 1 | import com.intellij.openapi.actionSystem.AnAction; 2 | import com.intellij.openapi.actionSystem.AnActionEvent; 3 | import com.intellij.openapi.actionSystem.CommonDataKeys; 4 | import com.intellij.openapi.actionSystem.Presentation; 5 | import com.intellij.openapi.editor.Document; 6 | import com.intellij.openapi.editor.Editor; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | import java.util.regex.Matcher; 10 | import java.util.regex.Pattern; 11 | 12 | public abstract class AbstractRunAction extends AnAction { 13 | public static class Block { 14 | public final String content; 15 | public final int lineStart; 16 | public final int lineEnd; 17 | 18 | public Block(String content, int lineStart, int lineEnd) { 19 | this.content = content; 20 | this.lineStart = lineStart; 21 | this.lineEnd = lineEnd; 22 | } 23 | } 24 | 25 | AbstractRunAction(String text) { 26 | super(text); 27 | } 28 | 29 | @Override 30 | public void actionPerformed(AnActionEvent e) { 31 | //System.out.println("RunCellAction"); 32 | Editor editor = CommonDataKeys.EDITOR.getData(e.getDataContext()); 33 | if (editor == null) { 34 | return; 35 | } 36 | 37 | Block block = findBlock(editor); 38 | if (block != null) { 39 | if (prefs.getTargetConsole() == Preferences.TARGET_INTERNAL_CONSOLE) { 40 | PythonConsoleUtils.execute(e, block.content); 41 | } else { 42 | Tmux.executeInTmux(prefs, block.content); 43 | } 44 | postExecuteHook(editor, block); 45 | } 46 | } 47 | 48 | @Override 49 | public void update(@NotNull AnActionEvent e) { 50 | // Always visible 51 | Presentation presentation = e.getPresentation(); 52 | presentation.setEnabled(true); 53 | presentation.setVisible(true); 54 | } 55 | 56 | protected final Preferences prefs = new Preferences(); 57 | 58 | protected void postExecuteHook(Editor editor, Block block) {} 59 | 60 | protected abstract Block findBlock(Editor editor); 61 | 62 | // The pattern defined in preferences (prefs.getDelimiterRegexp). This is a cache to avoid compiling 63 | // the pattern on every match 64 | private Pattern pattern; 65 | 66 | /** 67 | * Search for ## in the direction given (1 to search down, -1 to search up) 68 | * @param startLine the line where to start the search 69 | * @return the line on which ## was found or -1 if none is found 70 | */ 71 | protected static int searchForDoubleHash(Document document, int startLine, int direction) { 72 | int lineCount = document.getLineCount(); 73 | CharSequence text = document.getCharsSequence(); 74 | for (int line = startLine; line >= 0 && line < lineCount; line += direction) { 75 | int start = document.getLineStartOffset(line); 76 | int end = document.getLineEndOffset(line); 77 | if (end - start < 2) { 78 | continue; 79 | } 80 | 81 | if (startsWithDoubleHash(text, start, end)) { 82 | return line; 83 | } 84 | } 85 | return -1; 86 | } 87 | 88 | // Check if the first two non-space characters in the subseq delimited by (start, end) are ## 89 | protected static boolean startsWithDoubleHash(CharSequence seq, int start, int end) { 90 | for (int ci = start; ci < end; ++ci) { 91 | if (!Character.isWhitespace(seq.charAt(ci))) { 92 | if (ci < end - 1) { 93 | return seq.charAt(ci) == '#' && seq.charAt(ci + 1) == '#'; 94 | } else { 95 | return false; 96 | } 97 | } 98 | } 99 | return false; 100 | } 101 | 102 | /** 103 | * Search for delimiter in the direction given (1 to search down, -1 to search up) 104 | * @param startLine the line where to start the search 105 | * @return the line on which the delimiter was found or -1 if none is found 106 | */ 107 | protected int searchForDelimiter(Document document, int startLine, int direction) { 108 | // Update our pattern cache if the regexp has changed 109 | if (pattern == null || pattern.toString() != prefs.getDelimiterRegexp()) { 110 | pattern = Pattern.compile(prefs.getDelimiterRegexp()); 111 | //System.out.println("Compiling new pattern with delimiter : " + prefs.getDelimiterRegexp()); 112 | } 113 | 114 | int lineCount = document.getLineCount(); 115 | CharSequence text = document.getCharsSequence(); 116 | for (int line = startLine; line >= 0 && line < lineCount; line += direction) { 117 | int start = document.getLineStartOffset(line); 118 | int end = document.getLineEndOffset(line); 119 | if (end - start < 2) { 120 | continue; 121 | } 122 | 123 | if (matchesDelimiter(pattern, text, start, end)) { 124 | return line; 125 | } 126 | } 127 | return -1; 128 | } 129 | 130 | /** 131 | * Check if the subseq matches the delimiter defined in prefs 132 | */ 133 | protected boolean matchesDelimiter(Pattern pattern, CharSequence seq, int start, int end) { 134 | Matcher matcher = pattern.matcher(seq.subSequence(start, end)); 135 | return matcher.matches(); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/PythonConsoleUtils.java: -------------------------------------------------------------------------------- 1 | import com.google.common.collect.Lists; 2 | import com.intellij.execution.ExecutionHelper; 3 | import com.intellij.execution.console.LanguageConsoleView; 4 | import com.intellij.execution.process.ProcessHandler; 5 | import com.intellij.execution.ui.RunContentDescriptor; 6 | import com.intellij.openapi.actionSystem.AnActionEvent; 7 | import com.intellij.openapi.actionSystem.CommonDataKeys; 8 | import com.intellij.openapi.actionSystem.DataContext; 9 | import com.intellij.openapi.actionSystem.LangDataKeys; 10 | import com.intellij.openapi.editor.Editor; 11 | import com.intellij.openapi.module.Module; 12 | import com.intellij.openapi.project.Project; 13 | import com.intellij.util.Consumer; 14 | import com.intellij.util.NotNullFunction; 15 | import com.jetbrains.python.console.*; 16 | import com.jetbrains.python.console.pydev.PydevCompletionVariant; 17 | import org.jetbrains.annotations.NotNull; 18 | 19 | import java.util.Collection; 20 | import java.util.List; 21 | 22 | /** 23 | * Collection of utilities to interact with the internal python console. 24 | * Mostly copied from PyExecuteSelectionAction from pycharm's codebase 25 | */ 26 | public class PythonConsoleUtils { 27 | public static void execute(final AnActionEvent e, final String selectionText) { 28 | final Editor editor = CommonDataKeys.EDITOR.getData(e.getDataContext()); 29 | Project project = CommonDataKeys.PROJECT.getData(e.getDataContext()); 30 | Module module = e.getData(LangDataKeys.MODULE); 31 | 32 | findCodeExecutor(e, new Consumer() { 33 | @Override 34 | public void consume(PyCodeExecutor codeExecutor) { 35 | executeInConsole(codeExecutor, selectionText, editor); 36 | } 37 | }, editor, project, module); 38 | } 39 | 40 | private static void selectConsole(@NotNull DataContext dataContext, @NotNull Project project, 41 | final Consumer consumer) { 42 | Collection consoles = getConsoles(project); 43 | 44 | ExecutionHelper 45 | .selectContentDescriptor(dataContext, project, consoles, "Select console to execute in", new Consumer() { 46 | @Override 47 | public void consume(RunContentDescriptor descriptor) { 48 | if (descriptor != null && descriptor.getExecutionConsole() instanceof PyCodeExecutor) { 49 | consumer.consume((PyCodeExecutor) descriptor.getExecutionConsole()); 50 | } 51 | } 52 | }); 53 | } 54 | 55 | private static Collection getConsoles(Project project) { 56 | PythonConsoleToolWindow toolWindow = PythonConsoleToolWindow.getInstance(project); 57 | 58 | if (toolWindow != null && toolWindow.getToolWindow().isVisible()) { 59 | RunContentDescriptor selectedContentDescriptor = toolWindow.getSelectedContentDescriptor(); 60 | return selectedContentDescriptor != null ? Lists.newArrayList(selectedContentDescriptor) : Lists.newArrayList(); 61 | } 62 | 63 | Collection descriptors = 64 | ExecutionHelper.findRunningConsole(project, new NotNullFunction() { 65 | @NotNull 66 | @Override 67 | public Boolean fun(RunContentDescriptor dom) { 68 | return dom.getExecutionConsole() instanceof PyCodeExecutor && isAlive(dom); 69 | } 70 | }); 71 | 72 | if (descriptors.isEmpty() && toolWindow != null) { 73 | return toolWindow.getConsoleContentDescriptors(); 74 | } 75 | else { 76 | return descriptors; 77 | } 78 | } 79 | 80 | private static void findCodeExecutor(AnActionEvent e, Consumer consumer, Editor editor, Project project, Module module) { 81 | if (project != null && editor != null) { 82 | if (canFindConsole(e)) { 83 | selectConsole(e.getDataContext(), project, consumer); 84 | } 85 | else { 86 | startConsole(project, consumer, module); 87 | } 88 | } 89 | } 90 | 91 | private static boolean isAlive(RunContentDescriptor dom) { 92 | ProcessHandler processHandler = dom.getProcessHandler(); 93 | return processHandler != null && !processHandler.isProcessTerminated(); 94 | } 95 | 96 | private static void startConsole(final Project project, 97 | final Consumer consumer, 98 | Module context) { 99 | final PythonConsoleToolWindow toolWindow = PythonConsoleToolWindow.getInstance(project); 100 | 101 | if (toolWindow != null) { 102 | toolWindow.activate(new Runnable() { 103 | @Override 104 | public void run() { 105 | List descs = toolWindow.getConsoleContentDescriptors(); 106 | 107 | RunContentDescriptor descriptor = descs.get(0); 108 | if (descriptor != null && descriptor.getExecutionConsole() instanceof PyCodeExecutor) { 109 | consumer.consume((PyCodeExecutor)descriptor.getExecutionConsole()); 110 | } 111 | } 112 | }); 113 | } 114 | else { 115 | PythonConsoleRunnerFactory consoleRunnerFactory = PythonConsoleRunnerFactory.getInstance(); 116 | PydevConsoleRunner runner = consoleRunnerFactory.createConsoleRunner(project, null); 117 | runner.addConsoleListener(new PydevConsoleRunner.ConsoleListener() { 118 | @Override 119 | public void handleConsoleInitialized(LanguageConsoleView consoleView) { 120 | if (consoleView instanceof PyCodeExecutor) { 121 | consumer.consume((PyCodeExecutor)consoleView); 122 | } 123 | } 124 | }); 125 | runner.run(true); 126 | } 127 | } 128 | 129 | private static boolean canFindConsole(AnActionEvent e) { 130 | Project project = CommonDataKeys.PROJECT.getData(e.getDataContext()); 131 | if (project != null) { 132 | Collection descriptors = getConsoles(project); 133 | return descriptors.size() > 0; 134 | } 135 | else { 136 | return false; 137 | } 138 | } 139 | 140 | private static void executeInConsole(@NotNull PyCodeExecutor codeExecutor, @NotNull String text, Editor editor) { 141 | codeExecutor.executeCode(text, editor); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /.idea/uiDesigner.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /src/CellModeConfigurable.form: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 |
191 | -------------------------------------------------------------------------------- /.idea/modules/PythonCellMode.test.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | -------------------------------------------------------------------------------- /.idea/modules/PythonCellMode.main.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | --------------------------------------------------------------------------------