smart indent on paste for Python (PY-208)

This commit is contained in:
Dmitry Jemerov
2010-09-07 23:11:11 +04:00
parent 120e8b5b69
commit de5270972c
6 changed files with 221 additions and 6 deletions
@@ -20,6 +20,7 @@ import com.intellij.codeInsight.daemon.impl.CollectHighlightsUtil;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.RangeMarker;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.DumbService;
@@ -108,7 +109,11 @@ public class CopyPasteReferenceProcessor implements CopyPastePostProcessor<Refer
return null;
}
public void processTransferableData(final Project project, final Editor editor, final RangeMarker bounds, final ReferenceTransferableData value) {
public void processTransferableData(final Project project,
final Editor editor,
final RangeMarker bounds,
int caretColumn,
Ref<Boolean> indented, final ReferenceTransferableData value) {
if (DumbService.getInstance(project).isDumb()) {
return;
}
@@ -22,6 +22,7 @@ import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.FoldRegion;
import com.intellij.openapi.editor.RangeMarker;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Ref;
import com.intellij.psi.PsiFile;
import org.jetbrains.annotations.Nullable;
@@ -79,7 +80,12 @@ public class CopyPasteFoldingProcessor implements CopyPastePostProcessor<Folding
return null;
}
public void processTransferableData(final Project project, final Editor editor, final RangeMarker bounds, final FoldingTransferableData value) {
public void processTransferableData(final Project project,
final Editor editor,
final RangeMarker bounds,
int caretColumn,
Ref<Boolean> indented,
final FoldingTransferableData value) {
final CodeFoldingManagerImpl foldingManager = (CodeFoldingManagerImpl)CodeFoldingManager.getInstance(project);
foldingManager.updateFoldRegions(editor, true);
@@ -20,6 +20,7 @@ import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.RangeMarker;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Ref;
import com.intellij.psi.PsiFile;
import org.jetbrains.annotations.Nullable;
@@ -37,5 +38,10 @@ public interface CopyPastePostProcessor<T extends TextBlockTransferableData> {
@Nullable
T extractTransferableData(final Transferable content);
void processTransferableData(final Project project, final Editor editor, final RangeMarker bounds, final T value);
void processTransferableData(final Project project,
final Editor editor,
final RangeMarker bounds,
int caretColumn,
Ref<Boolean> indented,
final T value);
}
@@ -0,0 +1,65 @@
package com.intellij.codeInsight.editorActions;
import org.jetbrains.annotations.NonNls;
import java.awt.datatransfer.DataFlavor;
import java.io.Serializable;
/**
* @author yole
*/
public class IndentTransferableData implements TextBlockTransferableData, Serializable {
private static @NonNls DataFlavor ourFlavor;
private final int myIndent;
private final int myFirstLineLeadingSpaces;
public IndentTransferableData(int indent, int firstLineLeadingSpaces) {
myIndent = indent;
myFirstLineLeadingSpaces = firstLineLeadingSpaces;
}
public int getIndent() {
return myIndent;
}
public int getFirstLineLeadingSpaces() {
return myFirstLineLeadingSpaces;
}
public DataFlavor getFlavor() {
return getDataFlavorStatic();
}
public static DataFlavor getDataFlavorStatic() {
if (ourFlavor != null) {
return ourFlavor;
}
try {
ourFlavor = new DataFlavor(DataFlavor.javaJVMLocalObjectMimeType + ";class=" + IndentTransferableData.class.getName(), "Python indent transferable data");
}
catch (NoClassDefFoundError e) {
return null;
}
catch (IllegalArgumentException e) {
return null;
}
return ourFlavor;
}
public int getOffsetCount() {
return 0;
}
public int getOffsets(int[] offsets, int index) {
return index;
}
public int setOffsets(int[] offsets, int index) {
return index;
}
protected IndentTransferableData clone() {
return new IndentTransferableData(myIndent, myFirstLineLeadingSpaces);
}
}
@@ -33,6 +33,7 @@ import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.ide.CopyPasteManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
@@ -174,9 +175,10 @@ public class PasteHandler extends EditorActionHandler {
editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
editor.getSelectionModel().removeSelection();
final Ref<Boolean> indented = new Ref<Boolean>(Boolean.FALSE);
for(Map.Entry<CopyPastePostProcessor, TextBlockTransferableData> e: extraData.entrySet()) {
//noinspection unchecked
e.getKey().processTransferableData(project, editor, bounds, e.getValue());
e.getKey().processTransferableData(project, editor, bounds, col, indented, e.getValue());
}
final int indentOptions1 = indentOptions;
@@ -185,11 +187,15 @@ public class PasteHandler extends EditorActionHandler {
public void run() {
switch (indentOptions1) {
case CodeInsightSettings.INDENT_BLOCK:
indentBlock(project, editor, bounds.getStartOffset(), bounds.getEndOffset(), col);
if (!indented.get()) {
indentBlock(project, editor, bounds.getStartOffset(), bounds.getEndOffset(), col);
}
break;
case CodeInsightSettings.INDENT_EACH_LINE:
indentEachLine(project, editor, bounds.getStartOffset(), bounds.getEndOffset());
if (!indented.get()) {
indentEachLine(project, editor, bounds.getStartOffset(), bounds.getEndOffset());
}
break;
case CodeInsightSettings.REFORMAT_BLOCK:
@@ -0,0 +1,127 @@
package com.jetbrains.python.codeInsight;
import com.intellij.codeInsight.editorActions.CopyPastePostProcessor;
import com.intellij.codeInsight.editorActions.IndentTransferableData;
import com.intellij.codeStyle.CodeStyleFacade;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.RangeMarker;
import com.intellij.openapi.editor.actions.EditorActionUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiFile;
import com.intellij.util.text.CharArrayUtil;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.IOException;
/**
* @author yole
*/
public class CopyPasteIndentProcessor implements CopyPastePostProcessor<IndentTransferableData> {
@Override
public IndentTransferableData collectTransferableData(PsiFile file,
Editor editor,
int[] startOffsets,
int[] endOffsets) {
if (startOffsets.length != 1) {
return null;
}
Document document = editor.getDocument();
int selStartLine = document.getLineNumber(startOffsets [0]);
int selEndLine = document.getLineNumber(endOffsets [0]);
if (selStartLine == selEndLine) {
return null;
}
// check that selection starts at or before the first non-whitespace character on a line
for (int offset = startOffsets[0] - 1; offset >= document.getLineStartOffset(selStartLine); offset--) {
if (!Character.isWhitespace(document.getCharsSequence().charAt(offset))) {
return null;
}
}
int minIndent = Integer.MAX_VALUE;
int tabSize = CodeStyleFacade.getInstance(file.getProject()).getTabSize(file.getFileType());
for (int line = selStartLine; line <= selEndLine; line++) {
int start = document.getLineStartOffset(line);
int end = document.getLineEndOffset(line);
int indent = getIndent(document.getCharsSequence(), start, end, tabSize);
if (indent >= 0) {
minIndent = Math.min(minIndent, indent);
}
}
int firstNonSpaceChar = CharArrayUtil.shiftForward(document.getCharsSequence(), startOffsets [0], " \t");
int firstLineLeadingSpaces = (firstNonSpaceChar <= document.getLineEndOffset(selStartLine)) ? firstNonSpaceChar - startOffsets[0] : 0;
return new IndentTransferableData(minIndent, firstLineLeadingSpaces);
}
private static int getIndent(CharSequence chars, int start, int end, int tabSize) {
int result = 0;
boolean nonEmpty = false;
for(int i=start; i<end; i++) {
if (chars.charAt(i) == ' ') {
result++;
}
else if (chars.charAt(i) == '\t') {
result = ((result / tabSize) + 1) * tabSize;
}
else {
nonEmpty = true;
break;
}
}
return nonEmpty ? result : -1;
}
@Override
public IndentTransferableData extractTransferableData(Transferable content) {
IndentTransferableData indentData = null;
try {
final DataFlavor flavor = IndentTransferableData.getDataFlavorStatic();
if (flavor != null) {
indentData = (IndentTransferableData)content.getTransferData(flavor);
}
}
catch (UnsupportedFlavorException e) {
// do nothing
}
catch (IOException e) {
// do nothing
}
return indentData;
}
@Override
public void processTransferableData(final Project project,
final Editor editor,
final RangeMarker bounds,
final int caretColumn,
final Ref<Boolean> indented,
final IndentTransferableData value) {
if (value.getIndent() > 0) {
final Document document = editor.getDocument();
//System.out.println("--- before indent ---\n" + document.getText());
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
int startLine = document.getLineNumber(bounds.getStartOffset());
int endLine = document.getLineNumber(bounds.getEndOffset());
int startLineStart = document.getLineStartOffset(startLine);
// don't indent first line if there's any text before it
final String textBeforeFirstLine = document.getText(new TextRange(startLineStart, bounds.getStartOffset()));
if (textBeforeFirstLine.trim().length() == 0) {
EditorActionUtil.indentLine(project, editor, startLine, -value.getFirstLineLeadingSpaces());
}
for (int i = startLine+1; i <= endLine; i++) {
EditorActionUtil.indentLine(project, editor, i, caretColumn - value.getIndent());
}
indented.set(Boolean.TRUE);
}
});
//System.out.println("--- after indent ---\n" + document.getText());
}
}
}