├── .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 |
4 |
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 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
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 extends PsiElement> list, @NotNull Collection super LineMarkerInfo>> 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 |
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 | 
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 |
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 |
--------------------------------------------------------------------------------