2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com
.intellij
.formatting
;
19 import com
.intellij
.openapi
.util
.Comparing
;
20 import com
.intellij
.openapi
.util
.TextRange
;
21 import com
.intellij
.openapi
.util
.text
.StringUtil
;
22 import com
.intellij
.psi
.PsiElement
;
23 import com
.intellij
.psi
.PsiFile
;
24 import com
.intellij
.psi
.PsiWhiteSpace
;
25 import com
.intellij
.psi
.codeStyle
.CodeStyleSettings
;
26 import com
.intellij
.psi
.formatter
.FormattingDocumentModelImpl
;
27 import org
.jetbrains
.annotations
.NonNls
;
29 import java
.util
.ArrayList
;
32 private final int myStart
;
36 private int myIndentSpaces
;
38 private CharSequence myInitial
;
41 private static final byte FIRST
= 1;
42 private static final byte SAFE
= 0x2;
43 private static final byte KEEP_FIRST_COLUMN
= 0x4;
44 private static final byte LINE_FEEDS_ARE_READ_ONLY
= 0x8;
45 private static final byte READ_ONLY
= 0x10;
46 private static final byte CONTAINS_LF_INITIALLY
= 0x20;
47 private static final byte CONTAINS_SPACES_INITIALLY
= 0x40;
48 private static final int LF_COUNT_SHIFT
= 7;
49 private static final int MAX_LF_COUNT
= 1 << 24;
50 @NonNls private static final String CDATA_START
= "<![CDATA[";
51 @NonNls private static final String CDATA_END
= "]]>";
53 public WhiteSpace(int startOffset
, boolean isFirst
) {
54 myStart
= startOffset
;
56 setIsFirstWhiteSpace(isFirst
);
59 public void append(int newEndOffset
, FormattingDocumentModel model
, CodeStyleSettings
.IndentOptions options
) {
60 final int oldEndOffset
= myEnd
;
61 if (newEndOffset
== oldEndOffset
) return;
62 if (myStart
>= newEndOffset
) {
63 InitialInfoBuilder
.assertInvalidRanges(myStart
,
66 "some block intersects with whitespace"
71 TextRange range
= new TextRange(myStart
, myEnd
);
72 myInitial
= model
.getText(range
);
74 if (!coveredByBlock(model
)) {
75 InitialInfoBuilder
.assertInvalidRanges(myStart
,
78 "nonempty text is not covered by block"
82 final int tabsize
= options
.TAB_SIZE
;
83 for (int i
= oldEndOffset
- myStart
; i
< newEndOffset
- myStart
; i
++) {
84 switch (myInitial
.charAt(i
)) {
86 setLineFeeds(getLineFeeds() + 1);
94 myIndentSpaces
+= tabsize
;
99 if (getLineFeeds() > 0) myFlags
|= CONTAINS_LF_INITIALLY
;
100 else myFlags
&= ~CONTAINS_LF_INITIALLY
;
102 final int totalSpaces
= getTotalSpaces();
103 if (totalSpaces
> 0) myFlags
|= CONTAINS_SPACES_INITIALLY
;
104 else myFlags
&=~ CONTAINS_SPACES_INITIALLY
;
107 private boolean coveredByBlock(final FormattingDocumentModel model
) {
108 if (myInitial
== null) return true;
109 String s
= myInitial
.toString().trim();
110 if (s
.length() == 0) return true;
111 if (!(model
instanceof FormattingDocumentModelImpl
)) return false;
112 PsiFile psiFile
= ((FormattingDocumentModelImpl
)model
).getFile();
113 if (psiFile
== null) return false;
114 PsiElement start
= psiFile
.findElementAt(myStart
);
115 PsiElement end
= psiFile
.findElementAt(myEnd
-1);
116 if (s
.startsWith(CDATA_START
)) s
= s
.substring(CDATA_START
.length());
117 if (s
.endsWith(CDATA_END
)) s
= s
.substring(0, s
.length() - CDATA_END
.length());
119 if (s
.length() == 0) return true;
120 return start
== end
&& start
instanceof PsiWhiteSpace
; // there maybe non-white text inside CDATA-encoded injected elements
123 public String
generateWhiteSpace(CodeStyleSettings
.IndentOptions options
) {
124 StringBuilder buffer
= new StringBuilder();
125 StringUtil
.repeatSymbol(buffer
, '\n', getLineFeeds());
127 repeatTrailingSymbols(options
, buffer
, myIndentSpaces
, mySpaces
);
129 return buffer
.toString();
132 private static void repeatTrailingSymbols(final CodeStyleSettings
.IndentOptions options
,
133 final StringBuilder buffer
,
134 final int indentSpaces
,
136 if (options
.USE_TAB_CHARACTER
) {
137 if (options
.SMART_TABS
) {
138 int tabCount
= indentSpaces
/ options
.TAB_SIZE
;
139 int leftSpaces
= indentSpaces
- tabCount
* options
.TAB_SIZE
;
140 StringUtil
.repeatSymbol(buffer
, '\t', tabCount
);
141 StringUtil
.repeatSymbol(buffer
, ' ', leftSpaces
+ spaces
);
144 int size
= spaces
+ indentSpaces
;
146 if (size
>= options
.TAB_SIZE
) {
148 size
-= options
.TAB_SIZE
;
158 StringUtil
.repeatSymbol(buffer
, ' ', spaces
+ indentSpaces
);
162 public void setSpaces(final int spaces
, final int indent
) {
163 performModification(new Runnable() {
165 if (!isKeepFirstColumn() || (myFlags
& CONTAINS_SPACES_INITIALLY
) != 0) {
167 myIndentSpaces
= indent
;
173 private boolean doesNotContainAnySpaces() {
174 return getTotalSpaces() == 0 && getLineFeeds() == 0;
177 public int getStartOffset() {
181 public int getEndOffset() {
185 private void performModification(Runnable action
) {
186 if (isIsReadOnly()) return;
187 final boolean before
= doesNotContainAnySpaces();
188 final int lineFeedsBefore
= getLineFeeds();
190 if (isLineFeedsAreReadOnly()) {
191 setLineFeeds(lineFeedsBefore
);
194 final boolean after
= doesNotContainAnySpaces();
195 if (before
&& !after
) {
200 else if (!before
&& after
) {
207 public void arrangeSpaces(final SpacingImpl spaceProperty
) {
208 performModification(new Runnable() {
210 if (spaceProperty
!= null) {
211 if (getLineFeeds() == 0) {
212 if (getTotalSpaces() < spaceProperty
.getMinSpaces()) {
213 setSpaces(spaceProperty
.getMinSpaces(), 0);
215 if (getTotalSpaces() > spaceProperty
.getMaxSpaces()) {
216 setSpaces(spaceProperty
.getMaxSpaces(), 0);
226 public void arrangeLineFeeds(final SpacingImpl spaceProperty
, final FormatProcessor formatProcessor
) {
227 performModification(new Runnable() {
229 if (spaceProperty
!= null) {
230 spaceProperty
.refresh(formatProcessor
);
232 if (spaceProperty
.getMinLineFeeds() >= 0 && getLineFeeds() < spaceProperty
.getMinLineFeeds()) {
233 setLineFeeds(spaceProperty
.getMinLineFeeds());
235 if (getLineFeeds() > 0) {
236 if (spaceProperty
.getKeepBlankLines() > 0) {
237 if (getLineFeeds() >= spaceProperty
.getKeepBlankLines() + 1) {
238 setLineFeeds(spaceProperty
.getKeepBlankLines() + 1);
242 if (getLineFeeds() > spaceProperty
.getMinLineFeeds()) {
243 if (spaceProperty
.shouldKeepLineFeeds()) {
244 setLineFeeds(Math
.max(spaceProperty
.getMinLineFeeds(), 1));
247 setLineFeeds(spaceProperty
.getMinLineFeeds());
248 if (getLineFeeds() == 0) mySpaces
= 0;
252 if (getLineFeeds() == 1 && !spaceProperty
.shouldKeepLineFeeds() && spaceProperty
.getMinLineFeeds() == 0) {
257 if (getLineFeeds() > 0 && getLineFeeds() < spaceProperty
.getPrefLineFeeds()) {
258 setLineFeeds(spaceProperty
.getPrefLineFeeds());
262 } else if (isFirst()) {
271 public boolean containsLineFeeds() {
272 return isIsFirstWhiteSpace() || getLineFeeds() > 0;
275 public int getTotalSpaces() {
276 return mySpaces
+ myIndentSpaces
;
279 public void ensureLineFeed() {
280 performModification(new Runnable() {
282 if (!containsLineFeeds()) {
290 public boolean isReadOnly() {
291 return isIsReadOnly() || (isIsSafe() && doesNotContainAnySpaces());
294 public boolean equalsToString(CharSequence ws
) {
295 if (myInitial
== null) return ws
.length() == 0;
296 return Comparing
.equal(ws
,myInitial
,true);
299 public void setIsSafe(final boolean value
) {
300 setFlag(SAFE
, value
);
303 private void setFlag(final int mask
, final boolean value
) {
312 private boolean getFlag(final int mask
) {
313 return (myFlags
& mask
) != 0;
316 private boolean isFirst() {
317 return isIsFirstWhiteSpace();
320 public boolean containsLineFeedsInitially() {
321 if (myInitial
== null) return false;
322 return (myFlags
& CONTAINS_LF_INITIALLY
) != 0;
325 public void removeLineFeeds(final Spacing spacing
, final FormatProcessor formatProcessor
) {
326 performModification(new Runnable() {
333 arrangeLineFeeds((SpacingImpl
)spacing
, formatProcessor
);
334 arrangeSpaces((SpacingImpl
)spacing
);
337 public int getIndentOffset() {
338 return myIndentSpaces
;
341 public int getSpaces() {
345 public void setKeepFirstColumn(final boolean b
) {
346 setFlag(KEEP_FIRST_COLUMN
, b
);
349 public void setLineFeedsAreReadOnly() {
350 setLineFeedsAreReadOnly(true);
353 public void setReadOnly(final boolean isReadOnly
) {
354 setIsReadOnly(isReadOnly
);
357 public boolean isIsFirstWhiteSpace() {
358 return getFlag(FIRST
);
361 public boolean isIsSafe() {
362 return getFlag(SAFE
);
365 public boolean isKeepFirstColumn() {
366 return getFlag(KEEP_FIRST_COLUMN
);
369 public boolean isLineFeedsAreReadOnly() {
370 return getFlag(LINE_FEEDS_ARE_READ_ONLY
);
373 public void setLineFeedsAreReadOnly(final boolean lineFeedsAreReadOnly
) {
374 setFlag(LINE_FEEDS_ARE_READ_ONLY
, lineFeedsAreReadOnly
);
377 public boolean isIsReadOnly() {
378 return getFlag(READ_ONLY
);
381 public void setIsReadOnly(final boolean isReadOnly
) {
382 setFlag(READ_ONLY
, isReadOnly
);
385 public void setIsFirstWhiteSpace(final boolean isFirstWhiteSpace
) {
386 setFlag(FIRST
, isFirstWhiteSpace
);
389 public StringBuilder
generateWhiteSpace(final CodeStyleSettings
.IndentOptions indentOptions
,
391 final IndentInfo indent
) {
392 final StringBuilder result
= new StringBuilder();
393 int currentOffset
= getStartOffset();
394 CharSequence
[] lines
= getInitialLines();
396 for (int i
= 0; i
< lines
.length
- 1 && currentOffset
+ lines
[i
].length() <= offset
; i
++) {
397 result
.append(lines
[i
]);
398 currentOffset
+= lines
[i
].length();
402 if (currentOffset
== offset
) {
407 final String newIndentSpaces
= indent
.generateNewWhiteSpace(indentOptions
);
408 result
.append(newIndentSpaces
);
409 appendNonWhitespaces(result
, lines
, currentLine
);
410 if (currentLine
+ 1 < lines
.length
) {
412 for (int i
= currentLine
+ 1; i
< lines
.length
- 1; i
++) {
413 result
.append(lines
[i
]);
416 appendNonWhitespaces(result
, lines
, lines
.length
-1);
417 result
.append(lines
[lines
.length
- 1]);
423 private static void appendNonWhitespaces(StringBuilder result
, CharSequence
[] lines
, int currentLine
) {
424 if (currentLine
!= lines
.length
&& !lines
[currentLine
].toString().matches("\\s*")) {
425 result
.append(lines
[currentLine
]);
429 private CharSequence
[] getInitialLines() {
430 if (myInitial
== null) return new CharSequence
[]{""};
431 final ArrayList
<CharSequence
> result
= new ArrayList
<CharSequence
>();
432 StringBuffer currentLine
= new StringBuffer();
433 for (int i
= 0; i
< myInitial
.length(); i
++) {
434 final char c
= myInitial
.charAt(i
);
436 result
.add(currentLine
);
437 currentLine
= new StringBuffer();
440 currentLine
.append(c
);
443 result
.add(currentLine
);
444 return result
.toArray(new CharSequence
[result
.size()]);
447 public int getIndentSpaces() {
448 return myIndentSpaces
;
451 public int getLength() {
452 return myEnd
- myStart
;
455 public final int getLineFeeds() {
456 return myFlags
>>> LF_COUNT_SHIFT
;
459 public void setLineFeeds(final int lineFeeds
) {
460 assert lineFeeds
< MAX_LF_COUNT
;
461 final int flags
= myFlags
;
462 myFlags
&= ~
0xFFFFFF80;
463 myFlags
|= (lineFeeds
<< LF_COUNT_SHIFT
);
465 assert getLineFeeds() == lineFeeds
;
466 assert (flags
& 0x7F) == (myFlags
& 0x7F);
469 public TextRange
getTextRange() {
470 return new TextRange(myStart
, myEnd
);