fb158f8d93bb8f7087a1136fc4d44922db129d7c
[idea/community.git] / platform / lang-impl / src / com / intellij / codeInsight / editorActions / CopyPasteIndentProcessor.java
1 package com.intellij.codeInsight.editorActions;
2
3 import com.intellij.codeStyle.CodeStyleFacade;
4 import com.intellij.openapi.application.ApplicationManager;
5 import com.intellij.openapi.editor.Document;
6 import com.intellij.openapi.editor.Editor;
7 import com.intellij.openapi.editor.RangeMarker;
8 import com.intellij.openapi.editor.actions.EditorActionUtil;
9 import com.intellij.openapi.extensions.Extensions;
10 import com.intellij.openapi.fileTypes.FileType;
11 import com.intellij.openapi.project.Project;
12 import com.intellij.openapi.util.Ref;
13 import com.intellij.openapi.util.TextRange;
14 import com.intellij.psi.PsiDocumentManager;
15 import com.intellij.psi.PsiFile;
16 import com.intellij.util.text.CharArrayUtil;
17
18 import java.awt.datatransfer.DataFlavor;
19 import java.awt.datatransfer.Transferable;
20 import java.awt.datatransfer.UnsupportedFlavorException;
21 import java.io.IOException;
22
23 /**
24  * @author yole
25  */
26 public class CopyPasteIndentProcessor implements CopyPastePostProcessor<IndentTransferableData> {
27   @Override
28   public IndentTransferableData collectTransferableData(PsiFile file,
29                                                           Editor editor,
30                                                           int[] startOffsets,
31                                                           int[] endOffsets) {
32     if (!acceptFileType(file.getFileType())) {
33       return null;
34     }
35
36     if (startOffsets.length != 1) {
37       return null;
38     }
39     Document document = editor.getDocument();
40     int selStartLine = document.getLineNumber(startOffsets[0]);
41     int selEndLine = document.getLineNumber(endOffsets[0]);
42     if (selStartLine == selEndLine) {
43       return null;
44     }
45     // check that selection starts at or before the first non-whitespace character on a line
46     for (int offset = startOffsets[0] - 1; offset >= document.getLineStartOffset(selStartLine); offset--) {
47       if (!Character.isWhitespace(document.getCharsSequence().charAt(offset))) {
48         return null;
49       }
50     }
51     int minIndent = Integer.MAX_VALUE;
52     int tabSize = CodeStyleFacade.getInstance(file.getProject()).getTabSize(file.getFileType());
53     for (int line = selStartLine; line <= selEndLine; line++) {
54       int start = document.getLineStartOffset(line);
55       int end = document.getLineEndOffset(line);
56       int indent = getIndent(document.getCharsSequence(), start, end, tabSize);
57       if (indent >= 0) {
58         minIndent = Math.min(minIndent, indent);
59       }
60     }
61     int firstNonSpaceChar = CharArrayUtil.shiftForward(document.getCharsSequence(), startOffsets[0], " \t");
62     int firstLineLeadingSpaces = (firstNonSpaceChar <= document.getLineEndOffset(selStartLine)) ? firstNonSpaceChar - startOffsets[0] : 0;
63     return new IndentTransferableData(minIndent, firstLineLeadingSpaces);
64   }
65
66   private static boolean acceptFileType(FileType fileType) {
67     for(PreserveIndentOnPasteBean bean: Extensions.getExtensions(PreserveIndentOnPasteBean.EP_NAME)) {
68       if (fileType.getName().equals(bean.fileType)) {
69         return true;
70       }
71     }
72     return false;
73   }
74
75   private static int getIndent(CharSequence chars, int start, int end, int tabSize) {
76     int result = 0;
77     boolean nonEmpty = false;
78     for(int i=start; i<end; i++)  {
79       if (chars.charAt(i) == ' ') {
80         result++;
81       }
82       else if (chars.charAt(i) == '\t') {
83         result = ((result / tabSize) + 1) * tabSize;
84       }
85       else {
86         nonEmpty = true;
87         break;
88       }
89     }
90     return nonEmpty ? result : -1;
91   }
92
93   @Override
94   public IndentTransferableData extractTransferableData(Transferable content) {
95     IndentTransferableData indentData = null;
96     try {
97       final DataFlavor flavor = IndentTransferableData.getDataFlavorStatic();
98       if (flavor != null) {
99         indentData = (IndentTransferableData)content.getTransferData(flavor);
100       }
101     }
102     catch (UnsupportedFlavorException e) {
103       // do nothing
104     }
105     catch (IOException e) {
106       // do nothing
107     }
108     return indentData;
109   }
110
111   @Override
112   public void processTransferableData(final Project project,
113                                       final Editor editor,
114                                       final RangeMarker bounds,
115                                       final int caretColumn,
116                                       final Ref<Boolean> indented,
117                                       final IndentTransferableData value) {
118     final Document document = editor.getDocument();
119     final PsiFile psiFile = PsiDocumentManager.getInstance(project).getPsiFile(document);
120     if (psiFile == null || !acceptFileType(psiFile.getFileType())) {
121       return;
122     }
123     //System.out.println("--- before indent ---\n" + document.getText());
124     ApplicationManager.getApplication().runWriteAction(new Runnable() {
125       @Override
126       public void run() {
127         int startLine = document.getLineNumber(bounds.getStartOffset());
128         int endLine = document.getLineNumber(bounds.getEndOffset());
129         int startLineStart = document.getLineStartOffset(startLine);
130         // don't indent first line if there's any text before it
131         final String textBeforeFirstLine = document.getText(new TextRange(startLineStart, bounds.getStartOffset()));
132         if (textBeforeFirstLine.trim().length() == 0) {
133           EditorActionUtil.indentLine(project, editor, startLine, -value.getFirstLineLeadingSpaces());
134         }
135         for (int i = startLine+1; i <= endLine; i++) {
136           EditorActionUtil.indentLine(project, editor, i, caretColumn - value.getIndent());
137         }
138         indented.set(Boolean.TRUE);
139       }
140     });
141     //System.out.println("--- after indent ---\n" + document.getText());
142   }
143 }