enter handler that inserts \ at line break position

This commit is contained in:
Dmitry Jemerov
2010-09-22 21:33:57 +04:00
parent 517c44dae1
commit ea0e086711
9 changed files with 205 additions and 1 deletions
@@ -23,6 +23,7 @@
<annotator language="Python" implementationClass="com.jetbrains.python.validation.PyDumbAwareAnnotator"/>
<backspaceHandlerDelegate implementation="com.jetbrains.python.editor.PythonBackspaceHandler"/>
<quoteHandler fileType="Python" className="com.jetbrains.python.editor.PythonQuoteHandler"/>
<enterHandlerDelegate implementation="com.jetbrains.python.editor.PythonEnterHandler"/>
<sdkType implementation="com.jetbrains.python.sdk.PythonSdkType"/>
<gotoClassContributor implementation="com.jetbrains.python.PyGotoClassContributor"/>
<gotoSymbolContributor implementation="com.jetbrains.python.PyGotoSymbolContributor"/>
@@ -0,0 +1,121 @@
package com.jetbrains.python.editor;
import com.intellij.codeInsight.editorActions.TypedHandler;
import com.intellij.codeInsight.editorActions.enter.EnterHandlerDelegate;
import com.intellij.ide.DataManager;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
import com.intellij.openapi.util.Ref;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.jetbrains.python.psi.*;
import org.jetbrains.annotations.Nullable;
/**
* @author yole
*/
public class PythonEnterHandler implements EnterHandlerDelegate {
private static final Class[] IMPLICIT_WRAP_CLASSES = new Class[]{
PsiComment.class,
PyStringLiteralExpression.class,
PyParenthesizedExpression.class,
PyListCompExpression.class,
PyDictCompExpression.class,
PySetCompExpression.class,
PyDictLiteralExpression.class,
PySetLiteralExpression.class,
PyListLiteralExpression.class,
PyArgumentList.class,
PyParameterList.class};
@Override
public Result preprocessEnter(PsiFile file,
Editor editor,
Ref<Integer> caretOffset,
Ref<Integer> caretAdvance,
DataContext dataContext,
EditorActionHandler originalHandler) {
if (!(file instanceof PyFile)) {
return Result.Continue;
}
Document doc = editor.getDocument();
PsiDocumentManager.getInstance(file.getProject()).commitDocument(doc);
final int offset = caretOffset.get();
final PsiElement element = file.findElementAt(offset);
if (element == null) {
return Result.Continue;
}
PsiElement statementBefore = findStatementBeforeCaret(file, offset);
PsiElement statementAfter = findStatementAfterCaret(file, offset);
if (statementBefore != statementAfter) { // Enter pressed at statement break
return Result.Continue;
}
if (statementBefore != null && PsiTreeUtil.hasErrorElements(statementBefore)) {
final Boolean autoWrapping = DataManager.getInstance().loadFromDataContext(dataContext, TypedHandler.AUTO_WRAP_LINE_IN_PROGRESS_KEY);
if (autoWrapping == null) {
// code is already bad, don't mess it up even further
return Result.Continue;
}
// if we're in middle of typing, it's expected that we will have error elements
}
PsiElement wrappableBefore = findBeforeCaret(file, offset, IMPLICIT_WRAP_CLASSES);
PsiElement wrappableAfter = findAfterCaret(file, offset, IMPLICIT_WRAP_CLASSES);
while (wrappableBefore != null) {
PsiElement next = PsiTreeUtil.getParentOfType(wrappableBefore, IMPLICIT_WRAP_CLASSES);
if (next == null) {
break;
}
wrappableBefore = next;
}
while (wrappableAfter != null) {
PsiElement next = PsiTreeUtil.getParentOfType(wrappableAfter, IMPLICIT_WRAP_CLASSES);
if (next == null) {
break;
}
wrappableAfter = next;
}
if (wrappableAfter == null || wrappableBefore != wrappableAfter) {
doc.insertString(offset, "\\");
caretOffset.set(offset+1);
}
return Result.Continue;
}
@Nullable
private static PsiElement findStatementBeforeCaret(PsiFile file, int offset) {
return findBeforeCaret(file, offset, PyStatement.class);
}
@Nullable
private static PsiElement findStatementAfterCaret(PsiFile file, int offset) {
return findAfterCaret(file, offset, PyStatement.class);
}
@Nullable
private static PsiElement findBeforeCaret(PsiFile file, int offset, Class<? extends PsiElement>... classes) {
while(offset > 0) {
offset--;
final PsiElement element = file.findElementAt(offset);
if (!(element instanceof PsiWhiteSpace)) {
return PsiTreeUtil.getParentOfType(element, classes);
}
}
return null;
}
@Nullable
private static PsiElement findAfterCaret(PsiFile file, int offset, Class<? extends PsiElement>... classes) {
while(offset < file.getTextLength()) {
final PsiElement element = file.findElementAt(offset);
if (!(element instanceof PsiWhiteSpace)) {
return PsiTreeUtil.getParentOfType(element, classes);
}
offset++;
}
return null;
}
}
@@ -0,0 +1,4 @@
def copy_location(new_node, old_node):
for attr in 'lineno', 'col_offset':
if attr in old_node._attributes and attr in new_node._attributes and \
hasattr(old_node, attr):
+3
View File
@@ -0,0 +1,3 @@
def copy_location(new_node, old_node):
for attr in 'lineno', 'col_offset':
if attr in old_node._attributes and attr in new_node._attributes <caret>
@@ -0,0 +1,2 @@
# Gallia est omnis divisa in partes tres, quarum unam incolumnt Belgae, aliam
# Aquitani
+1
View File
@@ -0,0 +1 @@
# Gallia est omnis divisa in partes tres, quarum unam incolumnt Belgae, aliam <caret>
@@ -97,7 +97,33 @@ public class PyEditingTest extends PyLightFixtureTestCase {
}
public void testEnterInLineComment() { // PY-1739
assertEquals("# foo\n# bar", doTestTyping("# foo bar", 5, '\n'));
doTestEnter("# foo <caret>bar", "# foo \n# bar");
}
public void testEnterInStatement() {
doTestEnter("if a <caret>and b: pass", "if a \\\nand b: pass");
}
public void testEnterBeforeStatement() {
doTestEnter("def foo(): <caret>pass", "def foo(): \n pass");
}
public void testEnterInParameterList() {
doTestEnter("def foo(a,<caret>b): pass", "def foo(a,\n b): pass");
}
public void testEnterInTuple() {
doTestEnter("for x in 'a', <caret>'b': pass", "for x in 'a', \\\n 'b': pass");
}
public void testEnterInCodeWithErrorElements() {
doTestEnter("z=1 <caret>2", "z=1 \n2");
}
private void doTestEnter(String before, final String after) {
int pos = before.indexOf("<caret>");
before = before.replace("<caret>", "");
assertEquals(after, doTestTyping(before, pos, '\n'));
}
private String doTestTyping(final String text, final int offset, final char character) {
@@ -0,0 +1,45 @@
package com.jetbrains.python;
import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
import com.jetbrains.python.fixtures.PyLightFixtureTestCase;
/**
* @author yole
*/
public class PyWrapTest extends PyLightFixtureTestCase {
private boolean myOldWrap;
private int myOldMargin;
@Override
protected void setUp() throws Exception {
super.setUp();
final CodeStyleSettings settings = CodeStyleSettingsManager.getInstance(myFixture.getProject()).getCurrentSettings();
myOldWrap = settings.WRAP_WHEN_TYPING_REACHES_RIGHT_MARGIN;
myOldMargin = settings.RIGHT_MARGIN;
settings.WRAP_WHEN_TYPING_REACHES_RIGHT_MARGIN = true;
settings.RIGHT_MARGIN = 80;
}
@Override
protected void tearDown() throws Exception {
final CodeStyleSettings settings = CodeStyleSettingsManager.getInstance(myFixture.getProject()).getCurrentSettings();
settings.WRAP_WHEN_TYPING_REACHES_RIGHT_MARGIN = myOldWrap;
settings.RIGHT_MARGIN = myOldMargin;
super.tearDown();
}
public void testBackslashOnWrap() {
doTest("and hasattr(old_node, attr):");
}
public void testWrapInComment() {
doTest("Aquitani");
}
private void doTest(final String textToType) {
myFixture.configureByFile("wrap/" + getTestName(false) + ".py");
myFixture.type(textToType);
myFixture.checkResultByFile("wrap/" + getTestName(false) + ".after.py");
}
}
@@ -20,6 +20,7 @@ public class PythonAllTestsSuite {
PythonParsingTest.class,
PyStringLiteralTest.class,
PyIndentTest.class,
PyWrapTest.class,
PyStatementPartsTest.class,
PythonHighlightingTest.class,
PyStubsTest.class,