diff --git a/python/src/META-INF/python-core.xml b/python/src/META-INF/python-core.xml
index c533c0ec1b0d..72222837670b 100644
--- a/python/src/META-INF/python-core.xml
+++ b/python/src/META-INF/python-core.xml
@@ -576,6 +576,7 @@
+
diff --git a/python/src/com/jetbrains/python/codeInsight/editorActions/PyTripleQuoteBackspaceDelegate.java b/python/src/com/jetbrains/python/codeInsight/editorActions/PyTripleQuoteBackspaceDelegate.java
new file mode 100644
index 000000000000..6b63ff91b2bb
--- /dev/null
+++ b/python/src/com/jetbrains/python/codeInsight/editorActions/PyTripleQuoteBackspaceDelegate.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2000-2015 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.codeInsight.editorActions;
+
+import com.intellij.codeInsight.CodeInsightSettings;
+import com.intellij.codeInsight.editorActions.BackspaceHandlerDelegate;
+import com.intellij.codeInsight.editorActions.QuoteHandler;
+import com.intellij.codeInsight.editorActions.TypedHandler;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.editor.ex.EditorEx;
+import com.intellij.openapi.editor.highlighter.HighlighterIterator;
+import com.intellij.psi.PsiFile;
+import com.jetbrains.python.editor.BaseQuoteHandler;
+
+public class PyTripleQuoteBackspaceDelegate extends BackspaceHandlerDelegate {
+ private boolean isTripleQuote;
+
+ @Override
+ public void beforeCharDeleted(char c, PsiFile file, Editor editor) {
+ isTripleQuote = false;
+ if (c == '"' || c == '\'' && CodeInsightSettings.getInstance().AUTOINSERT_PAIR_QUOTE) {
+ final QuoteHandler quoteHandler = TypedHandler.getQuoteHandler(file, editor);
+ if (quoteHandler == null || !(quoteHandler instanceof BaseQuoteHandler)) return;
+
+ final int offset = editor.getCaretModel().getCurrentCaret().getOffset();
+ String text = editor.getDocument().getText();
+ boolean mayBeTripleQuote = offset >= 3 && offset + 2 < text.length();
+ if (mayBeTripleQuote) {
+ HighlighterIterator iterator = ((EditorEx)editor).getHighlighter().createIterator(offset);
+ boolean hasTripleQuoteAfter = text.charAt(offset - 1) == c && text.charAt(offset - 2) == c && text.charAt(offset - 3) == c;
+ isTripleQuote = quoteHandler.isOpeningQuote(iterator, offset - 1) && hasTripleQuoteAfter;
+ }
+ }
+ }
+
+ @Override
+ public boolean charDeleted(char c, PsiFile file, Editor editor) {
+ if (isTripleQuote) {
+ int offset = editor.getCaretModel().getCurrentCaret().getOffset();
+ editor.getDocument().deleteString(offset - 2, offset + 3);
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/python/src/com/jetbrains/python/editor/BaseQuoteHandler.java b/python/src/com/jetbrains/python/editor/BaseQuoteHandler.java
index dac383da363b..0f0859ed300c 100644
--- a/python/src/com/jetbrains/python/editor/BaseQuoteHandler.java
+++ b/python/src/com/jetbrains/python/editor/BaseQuoteHandler.java
@@ -15,18 +15,21 @@
*/
package com.jetbrains.python.editor;
+import com.intellij.codeInsight.editorActions.MultiCharQuoteHandler;
import com.intellij.codeInsight.editorActions.SimpleTokenSetQuoteHandler;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.highlighter.HighlighterIterator;
+import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;
+import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
/**
* @author yole
*/
-public class BaseQuoteHandler extends SimpleTokenSetQuoteHandler {
+public class BaseQuoteHandler extends SimpleTokenSetQuoteHandler implements MultiCharQuoteHandler {
private final char[] ourAutoClosingChars; // we add auto-close quotes before these
@@ -44,17 +47,21 @@ public class BaseQuoteHandler extends SimpleTokenSetQuoteHandler {
return false;
}
CharSequence text = document.getCharsSequence();
- if (offset + 1 >= text.length() || Arrays.binarySearch(ourAutoClosingChars, text.charAt(offset + 1)) >= 0) {
+ boolean mayBeSingleQuote = offset + 1 >= text.length() || Arrays.binarySearch(ourAutoClosingChars, text.charAt(offset + 1)) >= 0;
+ boolean mayBeTripleQuote = offset + 4 >= text.length() || Arrays.binarySearch(ourAutoClosingChars, text.charAt(offset + 4)) >= 0;
+
+ if (mayBeTripleQuote) {
char the_quote = text.charAt(offset);
- // if we're next to two same quotes, don't auto-close, the user may want a triple quote
+ // if we're next to two same quotes, auto-close triple quote
if (
offset >= 2 &&
text.charAt(offset - 1) == the_quote &&
text.charAt(offset - 2) == the_quote &&
(offset < 3 || text.charAt(offset - 3) != the_quote)
) {
- return false;
+ return true;
}
+ }if (mayBeSingleQuote ) {
// handle string literal context
if (super.isOpeningQuote(iterator, offset)) {
return true;
@@ -83,8 +90,18 @@ public class BaseQuoteHandler extends SimpleTokenSetQuoteHandler {
@Override
protected boolean isNonClosedLiteral(HighlighterIterator iterator, CharSequence chars) {
- if (getLiteralStartOffset(chars, iterator.getStart()) >= iterator.getEnd() - 1) return true;
- if (chars.charAt(iterator.getEnd() - 1) != '\"' && chars.charAt(iterator.getEnd() - 1) != '\'') return true;
+ int end = iterator.getEnd();
+ if (getLiteralStartOffset(chars, iterator.getStart()) >= end - 1) return true;
+ char end_symbol = chars.charAt(end - 1);
+ if (end_symbol != '"' && end_symbol != '\'') return true;
+
+ //for triple quoted string
+ if (end >= 3 &&
+ (end_symbol == chars.charAt(end - 2)) && (chars.charAt(end - 2) == chars.charAt(end - 3)) &&
+ (end < 4 || chars.charAt(end - 4) != end_symbol)) {
+ return true;
+ }
+
return false;
}
@@ -109,4 +126,23 @@ public class BaseQuoteHandler extends SimpleTokenSetQuoteHandler {
return false;
}
+
+ @Nullable
+ @Override
+ public CharSequence getClosingQuote(HighlighterIterator iterator, int offset) {
+ Document document = iterator.getDocument();
+ String text = document.getText();
+ char the_quote = text.charAt(offset - 1);
+ if (
+ offset >= 2 &&
+ text.charAt(offset - 2) == the_quote &&
+ text.charAt(offset - 3) == the_quote &&
+ (offset < 4 || text.charAt(offset - 4) != the_quote)) {
+ return StringUtil.repeat(String.valueOf(the_quote), 3);
+ }
+ else if (super.isOpeningQuote(iterator, offset - 1)) {
+ return String.valueOf(the_quote);
+ }
+ return null;
+ }
}
diff --git a/python/testData/editing/closedTripleQuoteBackspace.after.py b/python/testData/editing/closedTripleQuoteBackspace.after.py
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/python/testData/editing/closedTripleQuoteBackspace.before.py b/python/testData/editing/closedTripleQuoteBackspace.before.py
new file mode 100644
index 000000000000..c1ce811b0188
--- /dev/null
+++ b/python/testData/editing/closedTripleQuoteBackspace.before.py
@@ -0,0 +1 @@
+''''''
\ No newline at end of file
diff --git a/python/testSrc/com/jetbrains/python/PyEditingTest.java b/python/testSrc/com/jetbrains/python/PyEditingTest.java
index 88aad1427f61..22dc95ee7cbd 100644
--- a/python/testSrc/com/jetbrains/python/PyEditingTest.java
+++ b/python/testSrc/com/jetbrains/python/PyEditingTest.java
@@ -75,8 +75,12 @@ public class PyEditingTest extends PyTestCase {
assertEquals("'' ", doTestTyping(" ", 0, '\''));
}
- public void testNoClosingTriple() {
- assertEquals("'''", doTestTyping("''", 2, '\''));
+ public void testAutoCloseTriple() {
+ assertEquals("''''''", doTestTyping("''", 2, '\''));
+ }
+
+ public void testAutoRemoveTriple() {
+ doTestBackspace("closedTripleQuoteBackspace", new LogicalPosition(1, 3));
}
public void testOvertypeFromInside() {