update copyrights
[fedora-idea.git] / platform / lang-impl / src / com / intellij / formatting / WhiteSpace.java
blobf0f5788f5e82f3d0827c0d0e14918f9266909055
1 /*
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;
31 class WhiteSpace {
32 private final int myStart;
33 private int myEnd;
35 private int mySpaces;
36 private int myIndentSpaces;
38 private CharSequence myInitial;
39 private int myFlags;
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;
55 myEnd = 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,
64 newEndOffset,
65 model,
66 "some block intersects with whitespace"
70 myEnd = newEndOffset;
71 TextRange range = new TextRange(myStart, myEnd);
72 myInitial = model.getText(range);
74 if (!coveredByBlock(model)) {
75 InitialInfoBuilder.assertInvalidRanges(myStart,
76 myEnd,
77 model,
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)) {
85 case '\n':
86 setLineFeeds(getLineFeeds() + 1);
87 mySpaces = 0;
88 myIndentSpaces = 0;
89 break;
90 case ' ':
91 mySpaces++;
92 break;
93 case '\t':
94 myIndentSpaces += tabsize;
95 break;
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());
118 s = s.trim();
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,
135 final int spaces) {
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);
143 else {
144 int size = spaces + indentSpaces;
145 while (size > 0) {
146 if (size >= options.TAB_SIZE) {
147 buffer.append('\t');
148 size -= options.TAB_SIZE;
150 else {
151 buffer.append(' ');
152 size--;
157 else {
158 StringUtil.repeatSymbol(buffer, ' ', spaces + indentSpaces);
162 public void setSpaces(final int spaces, final int indent) {
163 performModification(new Runnable() {
164 public void run() {
165 if (!isKeepFirstColumn() || (myFlags & CONTAINS_SPACES_INITIALLY) != 0) {
166 mySpaces = spaces;
167 myIndentSpaces = indent;
173 private boolean doesNotContainAnySpaces() {
174 return getTotalSpaces() == 0 && getLineFeeds() == 0;
177 public int getStartOffset() {
178 return myStart;
181 public int getEndOffset() {
182 return myEnd;
185 private void performModification(Runnable action) {
186 if (isIsReadOnly()) return;
187 final boolean before = doesNotContainAnySpaces();
188 final int lineFeedsBefore = getLineFeeds();
189 action.run();
190 if (isLineFeedsAreReadOnly()) {
191 setLineFeeds(lineFeedsBefore);
193 if (isIsSafe()) {
194 final boolean after = doesNotContainAnySpaces();
195 if (before && !after) {
196 mySpaces = 0;
197 myIndentSpaces = 0;
198 setLineFeeds(0);
200 else if (!before && after) {
201 mySpaces = 1;
202 myIndentSpaces = 0;
207 public void arrangeSpaces(final SpacingImpl spaceProperty) {
208 performModification(new Runnable() {
209 public void run() {
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() {
228 public void run() {
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);
241 else {
242 if (getLineFeeds() > spaceProperty.getMinLineFeeds()) {
243 if (spaceProperty.shouldKeepLineFeeds()) {
244 setLineFeeds(Math.max(spaceProperty.getMinLineFeeds(), 1));
246 else {
247 setLineFeeds(spaceProperty.getMinLineFeeds());
248 if (getLineFeeds() == 0) mySpaces = 0;
252 if (getLineFeeds() == 1 && !spaceProperty.shouldKeepLineFeeds() && spaceProperty.getMinLineFeeds() == 0) {
253 setLineFeeds(0);
254 mySpaces = 0;
257 if (getLineFeeds() > 0 && getLineFeeds() < spaceProperty.getPrefLineFeeds()) {
258 setLineFeeds(spaceProperty.getPrefLineFeeds());
262 } else if (isFirst()) {
263 setLineFeeds(0);
264 mySpaces = 0;
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() {
281 public void run() {
282 if (!containsLineFeeds()) {
283 setLineFeeds(1);
284 mySpaces = 0;
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) {
304 if (value) {
305 myFlags |= mask;
307 else {
308 myFlags &= ~mask;
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() {
327 public void run() {
328 setLineFeeds(0);
329 mySpaces = 0;
330 myIndentSpaces = 0;
333 arrangeLineFeeds((SpacingImpl)spacing, formatProcessor);
334 arrangeSpaces((SpacingImpl)spacing);
337 public int getIndentOffset() {
338 return myIndentSpaces;
341 public int getSpaces() {
342 return mySpaces;
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,
390 final int offset,
391 final IndentInfo indent) {
392 final StringBuilder result = new StringBuilder();
393 int currentOffset = getStartOffset();
394 CharSequence[] lines = getInitialLines();
395 int currentLine = 0;
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();
399 result.append('\n');
400 currentOffset++;
401 currentLine++;
402 if (currentOffset == offset) {
403 break;
407 final String newIndentSpaces = indent.generateNewWhiteSpace(indentOptions);
408 result.append(newIndentSpaces);
409 appendNonWhitespaces(result, lines, currentLine);
410 if (currentLine + 1 < lines.length) {
411 result.append('\n');
412 for (int i = currentLine + 1; i < lines.length - 1; i++) {
413 result.append(lines[i]);
414 result.append('\n');
416 appendNonWhitespaces(result, lines, lines.length-1);
417 result.append(lines[lines.length - 1]);
420 return result;
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);
435 if (c == '\n') {
436 result.add(currentLine);
437 currentLine = new StringBuffer();
439 else {
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);