summaryrefslogtreecommitdiff
path: root/platform/lang-impl/src/com/intellij/codeInsight/editorActions/fillParagraph/ParagraphFillHandler.java
blob: 03a72378838cc98ddb380ac4da8f5fb06d423ce4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
package com.intellij.codeInsight.editorActions.fillParagraph;

import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.CharFilter;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
import com.intellij.psi.impl.source.codeStyle.CodeFormatterFacade;
import com.intellij.psi.tree.IElementType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.List;

/**
 * Defines general re-flow paragraph functionality.
 * Serves plain text files.
 *
 * User : ktisha
 */
public class ParagraphFillHandler {

  protected void performOnElement(@NotNull final PsiElement element, @NotNull final Editor editor) {
    final Document document = editor.getDocument();

    final TextRange textRange = getTextRange(element, editor);
    if (textRange.isEmpty()) return;
    final String text = textRange.substring(element.getContainingFile().getText());

    final List<String> subStrings = StringUtil.split(text, "\n", true);
    final String prefix = getPrefix(element);
    final String postfix = getPostfix(element);

    final StringBuilder stringBuilder = new StringBuilder();
    appendPrefix(element, text, stringBuilder);

    for (String string : subStrings) {
      final String startTrimmed = StringUtil.trimStart(string.trim(), prefix.trim());
      final String str = StringUtil.trimEnd(startTrimmed, postfix.trim());
      final String finalString = str.trim();
      if (!StringUtil.isEmptyOrSpaces(finalString))
        stringBuilder.append(finalString).append(" ");
    }
    appendPostfix(element, text, stringBuilder);

    final String replacementText = stringBuilder.toString();

    CommandProcessor.getInstance().executeCommand(element.getProject(), new Runnable() {
      @Override
      public void run() {
        document.replaceString(textRange.getStartOffset(), textRange.getEndOffset(),
                               replacementText);
        final CodeFormatterFacade codeFormatter = new CodeFormatterFacade(
                                        CodeStyleSettingsManager.getSettings(element.getProject()));
        codeFormatter.doWrapLongLinesIfNecessary(editor, element.getProject(), document,
                                                 textRange.getStartOffset(),
                                                 textRange.getStartOffset() + replacementText.length() + 1);
      }
    }, null, document);

  }

  protected void appendPostfix(@NotNull final PsiElement element,
                               @NotNull final String text,
                               @NotNull final StringBuilder stringBuilder) {
    final String postfix = getPostfix(element);
    if (text.endsWith(postfix.trim()))
      stringBuilder.append(postfix);
  }

  protected void appendPrefix(@NotNull final PsiElement element,
                              @NotNull final String text,
                              @NotNull final StringBuilder stringBuilder) {
    final String prefix = getPrefix(element);
    if (text.startsWith(prefix.trim()))
      stringBuilder.append(prefix);
  }

  private TextRange getTextRange(@NotNull final PsiElement element, @NotNull final Editor editor) {
    int startOffset = getStartOffset(element, editor);
    int endOffset = getEndOffset(element, editor);
    return TextRange.create(startOffset, endOffset);
  }

  private int getStartOffset(@NotNull final PsiElement element, @NotNull final Editor editor) {
    if (isBunchOfElement(element)) {
      final PsiElement firstElement = getFirstElement(element);
      return firstElement != null? firstElement.getTextRange().getStartOffset()
                                        : element.getTextRange().getStartOffset();
    }
    final int offset = editor.getCaretModel().getOffset();
    final int elementTextOffset = element.getTextOffset();
    final Document document = editor.getDocument();
    int lineNumber = document.getLineNumber(offset);

    while (lineNumber != document.getLineNumber(elementTextOffset)) {
      final String text = document.getText(TextRange.create(document.getLineStartOffset(lineNumber),
                                                            document.getLineEndOffset(lineNumber)));
      if (StringUtil.isEmptyOrSpaces(text)) {
        lineNumber += 1;
        break;
      }
      lineNumber -= 1;
    }
    final int lineStartOffset = document.getLineStartOffset(lineNumber);
    final String lineText = document
      .getText(TextRange.create(lineStartOffset, document.getLineEndOffset(lineNumber)));
    int shift = StringUtil.findFirst(lineText, CharFilter.NOT_WHITESPACE_FILTER);

    return lineStartOffset + shift;
  }

  protected boolean isBunchOfElement(PsiElement element) {
    return element instanceof PsiComment;
  }

  private int getEndOffset(@NotNull final PsiElement element, @NotNull final Editor editor) {
    if (isBunchOfElement(element)) {
      final PsiElement next = getLastElement(element);
      return next != null? next.getTextRange().getEndOffset()
                         : element.getTextRange().getEndOffset();
    }
    final int offset = editor.getCaretModel().getOffset();
    final int elementTextOffset = element.getTextRange().getEndOffset();
    final Document document = editor.getDocument();
    int lineNumber = document.getLineNumber(offset);

    while (lineNumber != document.getLineNumber(elementTextOffset)) {
      final String text = document.getText(TextRange.create(document.getLineStartOffset(lineNumber),
                                                            document.getLineEndOffset(lineNumber)));
      if (StringUtil.isEmptyOrSpaces(text)) {
        lineNumber -= 1;
        break;
      }
      lineNumber += 1;
    }
    return document.getLineEndOffset(lineNumber);
  }

  @Nullable
  private PsiElement getFirstElement(@NotNull final PsiElement element) {
    final IElementType elementType = element.getNode().getElementType();
    PsiElement prevSibling = element.getPrevSibling();
    PsiElement result = element;
    while (prevSibling != null && (prevSibling.getNode().getElementType().equals(elementType) ||
                                   (atWhitespaceToken(prevSibling) &&
                                   StringUtil.countChars(prevSibling.getText(), '\n') <= 1))) {
      String text = prevSibling.getText();
      final String prefix = getPrefix(element);
      final String postfix = getPostfix(element);
      text = StringUtil.trimStart(text.trim(), prefix.trim());
      text = StringUtil.trimEnd(text, postfix);

      if (prevSibling.getNode().getElementType().equals(elementType) &&
          StringUtil.isEmptyOrSpaces(text)) {
        break;
      }
      if (prevSibling.getNode().getElementType().equals(elementType))
        result = prevSibling;
      prevSibling = prevSibling.getPrevSibling();
    }
    return result;
  }

  @Nullable
  private PsiElement getLastElement(@NotNull final PsiElement element) {
    final IElementType elementType = element.getNode().getElementType();
    PsiElement nextSibling = element.getNextSibling();
    PsiElement result = element;
    while (nextSibling != null && (nextSibling.getNode().getElementType().equals(elementType) ||
                                   (atWhitespaceToken(nextSibling) &&
                                   StringUtil.countChars(nextSibling.getText(), '\n') <= 1))) {
      String text = nextSibling.getText();
      final String prefix = getPrefix(element);
      final String postfix = getPostfix(element);
      text = StringUtil.trimStart(text.trim(), prefix.trim());
      text = StringUtil.trimEnd(text, postfix);

      if (nextSibling.getNode().getElementType().equals(elementType) &&
          StringUtil.isEmptyOrSpaces(text)) {
        break;
      }
      if (nextSibling.getNode().getElementType().equals(elementType))
        result = nextSibling;
      nextSibling = nextSibling.getNextSibling();
    }
    return result;
  }

  protected boolean atWhitespaceToken(@Nullable final PsiElement element) {
    return element instanceof PsiWhiteSpace;
  }

  protected boolean isAvailableForElement(@Nullable final PsiElement element) {
    return element != null;
  }

  protected boolean isAvailableForFile(@Nullable final PsiFile psiFile) {
    return psiFile instanceof PsiPlainTextFile;
  }

  @NotNull
  protected String getPrefix(@NotNull final PsiElement element) {
    return "";
  }

  @NotNull
  protected String getPostfix(@NotNull final PsiElement element) {
    return "";
  }

}