Skip to content

Commit 8fa79f5

Browse files
committed
create a new action
1 parent f3bf95c commit 8fa79f5

File tree

3 files changed

+157
-110
lines changed

3 files changed

+157
-110
lines changed

python/src/META-INF/python-core-common.xml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -887,6 +887,19 @@
887887
<keyboard-shortcut keymap="NetBeans 6.5" first-keystroke="ctrl alt E" replace-all="true"/>
888888
</action>
889889

890+
<action id="SmartExecuteInPyConsoleAction"
891+
class="com.jetbrains.python.actions.PySmartExecuteSelectionAction"
892+
text="Smart execute selection in console"
893+
description="Smart executes selected code fragment in Python/Django console">
894+
<add-to-group group-id="EditorPopupMenu" anchor="before" relative-to-action="ExecuteInPyConsoleAction"/>
895+
896+
<keyboard-shortcut keymap="$default" first-keystroke="alt shift A"/>
897+
<keyboard-shortcut keymap="Mac OS X" first-keystroke="control shift A" />
898+
<keyboard-shortcut keymap="Mac OS X 10.5+" first-keystroke="control shift A" />
899+
<keyboard-shortcut keymap="Eclipse" first-keystroke="ctrl alt A" replace-all="true"/>
900+
<keyboard-shortcut keymap="NetBeans 6.5" first-keystroke="ctrl alt A" replace-all="true"/>
901+
</action>
902+
890903
<action id="PyRunFileInConsole" class="com.jetbrains.python.actions.PyRunFileInConsoleAction">
891904
<add-to-group group-id="EditorPopupMenu" anchor="after" relative-to-action="ExecuteInPyConsoleAction"/>
892905
</action>

python/src/com/jetbrains/python/actions/PyExecuteSelectionAction.java

Lines changed: 8 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,14 @@
1717
import com.intellij.openapi.wm.IdeFocusManager;
1818
import com.intellij.openapi.wm.ToolWindow;
1919
import com.intellij.psi.PsiDocumentManager;
20-
import com.intellij.psi.PsiElement;
2120
import com.intellij.psi.PsiFile;
22-
import com.intellij.psi.PsiWhiteSpace;
23-
import com.intellij.psi.util.PsiTreeUtil;
2421
import com.intellij.ui.content.Content;
2522
import com.intellij.ui.content.ContentManager;
2623
import com.intellij.util.Consumer;
27-
import com.intellij.util.DocumentUtil;
2824
import com.intellij.xdebugger.XDebugSession;
2925
import com.intellij.xdebugger.XDebuggerManager;
30-
import com.jetbrains.python.PyTokenTypes;
3126
import com.jetbrains.python.console.*;
32-
import com.jetbrains.python.psi.*;
33-
import com.jetbrains.python.psi.impl.PyIfPartElifImpl;
27+
import com.jetbrains.python.psi.PyFile;
3428
import com.jetbrains.python.run.PythonRunConfiguration;
3529
import org.jetbrains.annotations.NotNull;
3630
import org.jetbrains.annotations.Nullable;
@@ -46,106 +40,6 @@ public PyExecuteSelectionAction() {
4640
super(EXECUTE_SELECTION_IN_CONSOLE);
4741
}
4842

49-
50-
private static String getNLinesAfterCaret(Editor editor, int N) {
51-
VisualPosition caretPos = editor.getCaretModel().getVisualPosition();
52-
53-
Pair<LogicalPosition, LogicalPosition> lines = EditorUtil.calcSurroundingRange(editor, caretPos, caretPos);
54-
55-
LogicalPosition lineStart = lines.first;
56-
int start = editor.logicalPositionToOffset(lineStart);
57-
int end = DocumentUtil.getLineTextRange(editor.getDocument(), caretPos.getLine() + N).getEndOffset();
58-
return editor.getDocument().getCharsSequence().subSequence(start, end).toString();
59-
}
60-
61-
/*
62-
returns true if PsiElement not an evaluable Python statement
63-
*/
64-
private static boolean isPartialStatement(PsiElement psiElement) {
65-
return psiElement instanceof PyElsePart ||
66-
psiElement instanceof PyIfPartElifImpl ||
67-
psiElement instanceof PyIfPart ||
68-
psiElement instanceof PyWhilePart ||
69-
psiElement instanceof PyExceptPart ||
70-
psiElement instanceof PyFinallyPart ||
71-
psiElement instanceof PyStatementPart ||
72-
psiElement instanceof PyStatementList;
73-
}
74-
75-
/*
76-
closest parent that is evaluable
77-
*/
78-
private static PsiElement getEvaluableParent(PsiElement psiElement) {
79-
if (psiElement.getNode().getElementType() == PyTokenTypes.ELSE_KEYWORD ||
80-
psiElement.getNode().getElementType() == PyTokenTypes.ELIF_KEYWORD ||
81-
psiElement.getNode().getElementType() == PyTokenTypes.EXCEPT_KEYWORD ||
82-
psiElement.getNode().getElementType() == PyTokenTypes.FINALLY_KEYWORD) {
83-
psiElement = psiElement.getParent();
84-
}
85-
return isPartialStatement(psiElement) ? psiElement.getParent() : psiElement;
86-
}
87-
88-
private static void syntaxErrorAction(final AnActionEvent e) {
89-
showConsoleAndExecuteCode(e, "# syntax error");
90-
}
91-
92-
private static void smartExecuteCode(final AnActionEvent e, final Editor editor) {
93-
final Document document = editor.getDocument();
94-
final PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance(e.getProject());
95-
psiDocumentManager.commitDocument(document);
96-
final PsiFile psiFile = psiDocumentManager.getPsiFile(document);
97-
98-
final VisualPosition caretPos = editor.getCaretModel().getVisualPosition();
99-
final int line = caretPos.getLine();
100-
101-
final int offset = DocumentUtil.getFirstNonSpaceCharOffset(document, line);
102-
final PsiElement psiElement = psiFile.findElementAt(offset);
103-
int numLinesToSubmit = document.getLineCount() - line;
104-
PsiElement lastCommonParent = null;
105-
for (int i = 0; line + i < document.getLineCount(); ++i) {
106-
final int lineStartOffset = DocumentUtil.getFirstNonSpaceCharOffset(document, line + i);
107-
final PsiElement pe = psiFile.findElementAt(lineStartOffset);
108-
final PsiElement commonParentRaw = pe == null ? pe.getContainingFile() : PsiTreeUtil.findCommonParent(psiElement, pe);
109-
final PsiElement commonParent = getEvaluableParent(commonParentRaw);
110-
if (commonParent.getTextOffset() < offset ||
111-
commonParent instanceof PyFile) { // at new statement
112-
numLinesToSubmit = i;
113-
break;
114-
}
115-
lastCommonParent = commonParent;
116-
}
117-
if (lastCommonParent == null) {
118-
if (psiElement instanceof PsiWhiteSpace) { // if we are at a blank line
119-
moveCaretDown(editor);
120-
return;
121-
}
122-
syntaxErrorAction(e);
123-
return;
124-
}
125-
126-
String codeToSend =
127-
numLinesToSubmit == 0 ? "" :
128-
getNLinesAfterCaret(editor, numLinesToSubmit - 1);
129-
if (PsiTreeUtil.hasErrorElements(lastCommonParent) ||
130-
psiElement.getTextOffset() < offset) {
131-
codeToSend = null;
132-
}
133-
codeToSend = codeToSend == null ? null : codeToSend.trim();
134-
135-
if (codeToSend != null && !codeToSend.isEmpty()) {
136-
showConsoleAndExecuteCode(e, codeToSend);
137-
}
138-
if (codeToSend != null) {
139-
for (int i = 0; i < numLinesToSubmit; ++i) {
140-
moveCaretDown(editor);
141-
}
142-
}
143-
else {
144-
syntaxErrorAction(e);
145-
return;
146-
}
147-
}
148-
14943
@Override
15044
public void actionPerformed(@NotNull AnActionEvent e) {
15145
Editor editor = CommonDataKeys.EDITOR.getData(e.getDataContext());
@@ -155,12 +49,16 @@ public void actionPerformed(@NotNull AnActionEvent e) {
15549
showConsoleAndExecuteCode(e, selectionText);
15650
}
15751
else {
158-
smartExecuteCode(e, editor);
52+
String line = getLineUnderCaret(editor);
53+
if (line != null) {
54+
showConsoleAndExecuteCode(e, line.trim());
55+
moveCaretDown(editor);
56+
}
15957
}
16058
}
16159
}
16260

163-
private static void moveCaretDown(Editor editor) {
61+
static void moveCaretDown(Editor editor) {
16462
VisualPosition pos = editor.getCaretModel().getVisualPosition();
16563
Pair<LogicalPosition, LogicalPosition> lines = EditorUtil.calcSurroundingRange(editor, pos, pos);
16664
int offset = editor.getCaretModel().getOffset();
@@ -232,7 +130,7 @@ private static String getLineUnderCaret(Editor editor) {
232130
}
233131

234132
@Nullable
235-
private static String getSelectionText(@NotNull Editor editor) {
133+
static String getSelectionText(@NotNull Editor editor) {
236134
if (editor.getSelectionModel().hasSelection()) {
237135
SelectionModel model = editor.getSelectionModel();
238136

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2+
package com.jetbrains.python.actions;
3+
4+
import com.intellij.openapi.actionSystem.*;
5+
import com.intellij.openapi.editor.Document;
6+
import com.intellij.openapi.editor.Editor;
7+
import com.intellij.openapi.editor.LogicalPosition;
8+
import com.intellij.openapi.editor.VisualPosition;
9+
import com.intellij.openapi.editor.ex.util.EditorUtil;
10+
import com.intellij.openapi.util.Pair;
11+
import com.intellij.psi.PsiDocumentManager;
12+
import com.intellij.psi.PsiElement;
13+
import com.intellij.psi.PsiFile;
14+
import com.intellij.psi.PsiWhiteSpace;
15+
import com.intellij.psi.util.PsiTreeUtil;
16+
import com.intellij.util.DocumentUtil;
17+
import com.jetbrains.python.PyTokenTypes;
18+
import com.jetbrains.python.psi.*;
19+
import com.jetbrains.python.psi.impl.PyIfPartElifImpl;
20+
import org.jetbrains.annotations.NotNull;
21+
22+
public class PySmartExecuteSelectionAction extends AnAction {
23+
24+
private static String getNLinesAfterCaret(Editor editor, int N) {
25+
VisualPosition caretPos = editor.getCaretModel().getVisualPosition();
26+
27+
Pair<LogicalPosition, LogicalPosition> lines = EditorUtil.calcSurroundingRange(editor, caretPos, caretPos);
28+
29+
LogicalPosition lineStart = lines.first;
30+
int start = editor.logicalPositionToOffset(lineStart);
31+
int end = DocumentUtil.getLineTextRange(editor.getDocument(), caretPos.getLine() + N).getEndOffset();
32+
return editor.getDocument().getCharsSequence().subSequence(start, end).toString();
33+
}
34+
35+
/*
36+
returns true if PsiElement not an evaluable Python statement
37+
*/
38+
private static boolean isPartialStatement(PsiElement psiElement) {
39+
return psiElement instanceof PyElsePart ||
40+
psiElement instanceof PyIfPartElifImpl ||
41+
psiElement instanceof PyIfPart ||
42+
psiElement instanceof PyWhilePart ||
43+
psiElement instanceof PyExceptPart ||
44+
psiElement instanceof PyFinallyPart ||
45+
psiElement instanceof PyStatementPart ||
46+
psiElement instanceof PyStatementList;
47+
}
48+
49+
/*
50+
closest parent that is evaluable
51+
*/
52+
private static PsiElement getEvaluableParent(PsiElement psiElement) {
53+
if (psiElement.getNode().getElementType() == PyTokenTypes.ELSE_KEYWORD ||
54+
psiElement.getNode().getElementType() == PyTokenTypes.ELIF_KEYWORD ||
55+
psiElement.getNode().getElementType() == PyTokenTypes.EXCEPT_KEYWORD ||
56+
psiElement.getNode().getElementType() == PyTokenTypes.FINALLY_KEYWORD) {
57+
psiElement = psiElement.getParent();
58+
}
59+
return isPartialStatement(psiElement) ? psiElement.getParent() : psiElement;
60+
}
61+
62+
private static void syntaxErrorAction(final AnActionEvent e) {
63+
PyExecuteSelectionAction.showConsoleAndExecuteCode(e, "# syntax error");
64+
}
65+
66+
private static void smartExecuteCode(final AnActionEvent e, final Editor editor) {
67+
final Document document = editor.getDocument();
68+
final PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance(e.getProject());
69+
psiDocumentManager.commitDocument(document);
70+
final PsiFile psiFile = psiDocumentManager.getPsiFile(document);
71+
72+
final VisualPosition caretPos = editor.getCaretModel().getVisualPosition();
73+
final int line = caretPos.getLine();
74+
75+
final int offset = DocumentUtil.getFirstNonSpaceCharOffset(document, line);
76+
final PsiElement psiElement = psiFile.findElementAt(offset);
77+
int numLinesToSubmit = document.getLineCount() - line;
78+
PsiElement lastCommonParent = null;
79+
for (int i = 0; line + i < document.getLineCount(); ++i) {
80+
final int lineStartOffset = DocumentUtil.getFirstNonSpaceCharOffset(document, line + i);
81+
final PsiElement pe = psiFile.findElementAt(lineStartOffset);
82+
final PsiElement commonParentRaw = pe == null ? pe.getContainingFile() : PsiTreeUtil.findCommonParent(psiElement, pe);
83+
final PsiElement commonParent = getEvaluableParent(commonParentRaw);
84+
if (commonParent.getTextOffset() < offset ||
85+
commonParent instanceof PyFile) { // at new statement
86+
numLinesToSubmit = i;
87+
break;
88+
}
89+
lastCommonParent = commonParent;
90+
}
91+
if (lastCommonParent == null) {
92+
if (psiElement instanceof PsiWhiteSpace) { // if we are at a blank line
93+
PyExecuteSelectionAction.moveCaretDown(editor);
94+
return;
95+
}
96+
syntaxErrorAction(e);
97+
return;
98+
}
99+
100+
String codeToSend =
101+
numLinesToSubmit == 0 ? "" :
102+
getNLinesAfterCaret(editor, numLinesToSubmit - 1);
103+
if (PsiTreeUtil.hasErrorElements(lastCommonParent) ||
104+
psiElement.getTextOffset() < offset) {
105+
codeToSend = null;
106+
}
107+
codeToSend = codeToSend == null ? null : codeToSend.trim();
108+
109+
if (codeToSend != null && !codeToSend.isEmpty()) {
110+
PyExecuteSelectionAction.showConsoleAndExecuteCode(e, codeToSend);
111+
}
112+
if (codeToSend != null) {
113+
for (int i = 0; i < numLinesToSubmit; ++i) {
114+
PyExecuteSelectionAction.moveCaretDown(editor);
115+
}
116+
}
117+
else {
118+
syntaxErrorAction(e);
119+
return;
120+
}
121+
}
122+
123+
@Override
124+
public void actionPerformed(@NotNull AnActionEvent e) {
125+
Editor editor = CommonDataKeys.EDITOR.getData(e.getDataContext());
126+
if (editor != null) {
127+
final String selectionText = PyExecuteSelectionAction.getSelectionText(editor);
128+
if (selectionText != null) {
129+
PyExecuteSelectionAction.showConsoleAndExecuteCode(e, selectionText);
130+
}
131+
else {
132+
smartExecuteCode(e, editor);
133+
}
134+
}
135+
}
136+
}

0 commit comments

Comments
 (0)