| /* |
| * Copyright 2000-2012 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.intellij.formatting; |
| |
| import com.intellij.openapi.util.Comparing; |
| import com.intellij.openapi.util.TextRange; |
| import com.intellij.psi.PsiElement; |
| import com.intellij.psi.PsiFile; |
| import com.intellij.psi.PsiWhiteSpace; |
| import com.intellij.psi.codeStyle.CommonCodeStyleSettings; |
| import com.intellij.psi.formatter.FormattingDocumentModelImpl; |
| import com.intellij.util.text.CharArrayUtil; |
| import org.jetbrains.annotations.NotNull; |
| |
| import java.util.ArrayList; |
| |
| /** |
| * Object-level representation of continuous white space at document. Either line feed or tabulation or space is considered to be <code>'white-space'</code>. |
| * I.e. document text fragment like {@code '\t \t\t\n\t \t'} may be considered as a continuous white space and may be represented as a {@link WhiteSpace} object. |
| * <p/> |
| * Provides number of properties that describe encapsulated continuous white space: |
| * <ul> |
| * <li>{@link #getLineFeeds() lineFeeds};</li> |
| * <li>{@link #getSpaces() spaces};</li> |
| * <li>{@link #getIndentSpaces()};</li> |
| * </ul> |
| * <p/> |
| * Provides ability to build string representation of the managed settings taking into consideration user settings for tabulation vs white space usage, tabulation |
| * size etc. |
| * <p/> |
| * Not thread-safe. |
| */ |
| class WhiteSpace { |
| |
| private static final char LINE_FEED = '\n'; |
| |
| private final int myStart; |
| private int myEnd; |
| |
| private int mySpaces; |
| private int myIndentSpaces; |
| private int myInitialLastLinesSpaces; |
| private int myInitialLastLinesTabs; |
| |
| private CharSequence myInitial; |
| private int myFlags; |
| private boolean myForceSkipTabulationsUsage; |
| |
| private static final byte FIRST = 1; |
| private static final byte SAFE = 0x2; |
| private static final byte KEEP_FIRST_COLUMN = 0x4; |
| private static final byte LINE_FEEDS_ARE_READ_ONLY = 0x8; |
| private static final byte READ_ONLY = 0x10; |
| private static final byte CONTAINS_LF_INITIALLY = 0x20; |
| private static final byte CONTAINS_SPACES_INITIALLY = 0x40; |
| private static final int LF_COUNT_SHIFT = 7; |
| private static final int MAX_LF_COUNT = 1 << 24; |
| |
| /** |
| * Creates new <code>WhiteSpace</code> object with the given start offset and a flag that shows if current white space is |
| * the first white space. |
| * <p/> |
| * <b>Note:</b> {@link #getEndOffset() end offset} value is the same as the {@link #getStartOffset() start offset} for |
| * the newly constructed object. {@link #append(int, FormattingDocumentModel, CommonCodeStyleSettings.IndentOptions)} should be |
| * called in order to apply desired end offset. |
| * |
| * @param startOffset start offset to use |
| * @param isFirst flag that shows if current white space is the first |
| */ |
| public WhiteSpace(int startOffset, boolean isFirst) { |
| myStart = startOffset; |
| myEnd = startOffset; |
| setIsFirstWhiteSpace(isFirst); |
| } |
| |
| /** |
| * Applies new end offset to the current object. |
| * <p/> |
| * Namely, performs the following: |
| * <ol> |
| * <li>Checks if new end offset can be applied, return in case of negative answer;</li> |
| * <li> |
| * Processes all new symbols introduced by the new end offset value, calculates number of line feeds, |
| * white spaces and tabulations between them and updates {@link #getLineFeeds() lineFeeds}, {@link #getSpaces() spaces}, |
| * {@link #getIndentSpaces() indentSpaces} and {@link #getTotalSpaces() totalSpaces} properties accordingly; |
| * </li> |
| * </ol> |
| * |
| * @param newEndOffset new end offset value |
| * @param model formatting model that is used to access to the underlying document text |
| * @param options indent formatting options |
| */ |
| public void append(int newEndOffset, FormattingDocumentModel model, CommonCodeStyleSettings.IndentOptions options) { |
| final int oldEndOffset = myEnd; |
| if (newEndOffset == oldEndOffset) return; |
| if (myStart >= newEndOffset) { |
| InitialInfoBuilder.assertInvalidRanges(myStart, |
| newEndOffset, |
| model, |
| "some block intersects with whitespace" |
| ); |
| } |
| |
| myEnd = newEndOffset; |
| TextRange range = new TextRange(myStart, myEnd); |
| CharSequence oldText = myInitial; |
| myInitial = model.getText(range); |
| |
| if (!coveredByBlock(model)) { |
| InitialInfoBuilder.assertInvalidRanges(myStart, |
| myEnd, |
| model, |
| "nonempty text is not covered by block" |
| ); |
| } |
| |
| // There is a possible case that this method is called more than once on the same object. We want to |
| if (newEndOffset > oldEndOffset) { |
| refreshStateOnEndOffsetIncrease(newEndOffset, oldEndOffset, options.TAB_SIZE); |
| } else { |
| refreshStateOnEndOffsetDecrease(oldText, newEndOffset, oldEndOffset, options.TAB_SIZE); |
| } |
| IndentInside indent = IndentInside.getLastLineIndent(myInitial); |
| myInitialLastLinesSpaces = indent.whiteSpaces; |
| myInitialLastLinesTabs = indent.tabs; |
| |
| if (getLineFeeds() > 0) myFlags |= CONTAINS_LF_INITIALLY; |
| else myFlags &= ~CONTAINS_LF_INITIALLY; |
| |
| final int totalSpaces = getTotalSpaces(); |
| if (totalSpaces > 0) myFlags |= CONTAINS_SPACES_INITIALLY; |
| else myFlags &=~ CONTAINS_SPACES_INITIALLY; |
| } |
| |
| /** |
| * Allows to check if <code>'myInitial'</code> property value stands for continuous white space text. |
| * <p/> |
| * The text is considered to be continuous <code>'white space'</code> at following cases: |
| * <ul> |
| * <li><code>'myInitial'</code> is empty string or string that contains white spaces only;</li> |
| * <li><code>'myInitial'</code> is a <code>CDATA</code> string which content is empty or consists from white spaces only;</li> |
| * <li><code>'myInitial'</code> string belongs to the same {@link PsiWhiteSpace} element;</li> |
| * </ul> |
| * |
| * @param model formatting model that is used to provide access to the <code>PSI API</code> if necessary |
| * @return <code>true</code> if <code>'myInitial'</code> property value stands for white space; |
| * <code>false</code> otherwise |
| */ |
| private boolean coveredByBlock(final FormattingDocumentModel model) { |
| if (myInitial == null) return true; |
| if (model.containsWhiteSpaceSymbolsOnly(myStart, myEnd)) return true; |
| |
| if (!(model instanceof FormattingDocumentModelImpl)) return false; |
| PsiFile psiFile = ((FormattingDocumentModelImpl)model).getFile(); |
| if (psiFile == null) return false; |
| PsiElement start = psiFile.findElementAt(myStart); |
| PsiElement end = psiFile.findElementAt(myEnd-1); |
| return start == end && start instanceof PsiWhiteSpace; // there maybe non-white text inside CDATA-encoded injected elements |
| } |
| |
| private void refreshStateOnEndOffsetIncrease(int newEndOffset, int oldEndOffset, int tabSize) { |
| assert newEndOffset > oldEndOffset; |
| WhiteSpaceInfo info = parse(myInitial, oldEndOffset - myStart, newEndOffset - myStart, mySpaces + myIndentSpaces, tabSize); |
| if (info.lineFeeds > 0) { |
| setLineFeeds(getLineFeeds() + info.lineFeeds); |
| mySpaces = 0; |
| myIndentSpaces = 0; |
| } |
| mySpaces += info.spaces; |
| myIndentSpaces += info.indentSpaces; |
| } |
| |
| private void refreshStateOnEndOffsetDecrease(CharSequence oldText, int newEndOffset, int oldEndOffset, int tabSize) { |
| assert newEndOffset < oldEndOffset; |
| int lineFeedsNumberAtRemovedText = 0; |
| int spacesNumberAtRemovedText = 0; |
| int indentSpacesNumberAtRemovedText = 0; |
| int column = mySpaces + myIndentSpaces; |
| for (int i = oldEndOffset - 1; i >= newEndOffset; i--) { |
| switch (oldText.charAt(i)) { |
| case LINE_FEED: ++lineFeedsNumberAtRemovedText; column = 0; break; |
| case ' ': ++spacesNumberAtRemovedText; column--; break; |
| case '\t': |
| int change = column % tabSize; |
| if (change == 0) { |
| change = tabSize; |
| } |
| indentSpacesNumberAtRemovedText += change; column -= change; break; |
| } |
| } |
| |
| // There are no line feeds at remained text, hence, we can just subtract number of spaces and indent spaces |
| // from removed text and finish. |
| if (getLineFeeds() - lineFeedsNumberAtRemovedText <= 0) { |
| setLineFeeds(0); |
| mySpaces -= spacesNumberAtRemovedText; |
| myIndentSpaces -= indentSpacesNumberAtRemovedText; |
| return; |
| } |
| |
| // There are white spaces at remained text, hence, we need to calculate number of spaces and indent spaces between |
| // last line feed and new end offset. |
| int newLineFeedsNumber = getLineFeeds() - lineFeedsNumberAtRemovedText; |
| assert newLineFeedsNumber >= 0; |
| newLineFeedsNumber = newLineFeedsNumber < 0 ? 0 : newLineFeedsNumber; // Never expect the defense to be triggered. |
| setLineFeeds(newLineFeedsNumber); |
| int startOffset = CharArrayUtil.shiftForwardUntil(oldText, newEndOffset - 1, "\n") + 1; |
| WhiteSpaceInfo info = parse(oldText, startOffset, newEndOffset, 0, tabSize); |
| mySpaces = info.spaces; |
| myIndentSpaces = info.indentSpaces; |
| } |
| |
| /** |
| * Parses information about white space symbols at the target region of the given text. |
| * |
| * @param text target text |
| * @param startOffset target text range's start offset (inclusive) |
| * @param endOffset target text range's end offset (exclusive) |
| * @param startColumn given start offset's column. It affects how tab width is calculated, say, a tab symbol which |
| * occupies four columns, will occupy only three if located at the first column |
| * @param tabSize tab width in columns |
| * @return information about white space symbols at the target region of the given text |
| */ |
| @NotNull |
| private static WhiteSpaceInfo parse(@NotNull CharSequence text, int startOffset, int endOffset, int startColumn, int tabSize) { |
| assert startOffset <= endOffset; |
| |
| int spaces = 0; |
| int indentSpaces = 0; |
| int lineFeeds = 0; |
| int column = startColumn; |
| |
| for (int i = startOffset; i < endOffset; i++) { |
| switch (text.charAt(i)) { |
| case LINE_FEED: |
| lineFeeds++; |
| spaces = 0; |
| indentSpaces = 0; |
| column = 0; |
| break; |
| case '\t': |
| int change = tabSize - (column % tabSize); |
| indentSpaces += change; |
| column += change; |
| break; |
| default: spaces++; column++; |
| } |
| } |
| |
| return new WhiteSpaceInfo(lineFeeds, indentSpaces, spaces); |
| } |
| |
| /** |
| * Builds string that contains line feeds, white spaces and tabulation symbols known to the current {@link WhiteSpace} object. |
| * |
| * @param options indentation formatting options |
| * @return string that contains line feeds, white spaces and tabulation symbols known to the current |
| * {@link WhiteSpace} object |
| */ |
| public String generateWhiteSpace(CommonCodeStyleSettings.IndentOptions options) { |
| return new IndentInfo(getLineFeeds(), myIndentSpaces, mySpaces, myForceSkipTabulationsUsage).generateNewWhiteSpace(options); |
| } |
| |
| /** |
| * Tries to apply given values to {@link #getSpaces() spaces} and {@link #getIndentSpaces() indentSpaces} properties accordingly. |
| * <p/> |
| * The action is not guaranteed to be executed (i.e. the it's not guaranteed that target properties return given values after |
| * this method call - see {@link #performModification(Runnable)} for more details). |
| * <p/> |
| * Moreover, the action is guaranteed to be <b>not</b> executed if {@link #isKeepFirstColumn() keepFirstColumn} property |
| * is unset and target document string doesn't contain spaces. |
| * |
| * @param spaces new value for the {@link #getSpaces() spaces} property |
| * @param indent new value for the {@link #getIndentSpaces()} indentSpaces} property |
| */ |
| public void setSpaces(final int spaces, final int indent) { |
| performModification(new Runnable() { |
| @Override |
| public void run() { |
| if (!isKeepFirstColumn() || (myFlags & CONTAINS_SPACES_INITIALLY) != 0) { |
| mySpaces = spaces; |
| myIndentSpaces = indent; |
| } |
| } |
| }); |
| } |
| |
| private boolean doesNotContainAnySpaces() { |
| return getTotalSpaces() == 0 && getLineFeeds() == 0; |
| } |
| |
| public int getStartOffset() { |
| return myStart; |
| } |
| |
| public int getEndOffset() { |
| return myEnd; |
| } |
| |
| /** |
| * Execute given action in a safe manner. |
| * <p/> |
| * <code>'Safe manner'</code> here means the following: |
| * <ul> |
| * <li>don't execute the action if {@link #isIsReadOnly() isReadOnly} property value is set to <code>true</code>;</li> |
| * <li> |
| * ensure that number of line feeds after action execution is preserved if line feeds are |
| * {@link #isLineFeedsAreReadOnly() read only}; |
| * </li> |
| * <li> |
| * ensure the following if {@link #isIsSafe() isSafe} property is set to <code>true</code>: |
| * <ul> |
| * <li> |
| * cut all white spaces and line feeds appeared after given action execution to single white space if there |
| * were no line feeds and white spaces before; |
| * </li> |
| * </ul> |
| * </li> |
| * </ul> |
| * |
| * @param action action that should be performed in a safe manner |
| */ |
| private void performModification(Runnable action) { |
| if (isIsReadOnly()) return; |
| final boolean before = doesNotContainAnySpaces(); |
| final int lineFeedsBefore = getLineFeeds(); |
| action.run(); |
| if (isLineFeedsAreReadOnly()) { |
| setLineFeeds(lineFeedsBefore); |
| } |
| if (isIsSafe()) { |
| final boolean after = doesNotContainAnySpaces(); |
| if (before && !after) { |
| // Actions below seem to be useless if 'after' value is 'false'. Are kept as historical heritage. |
| mySpaces = 0; |
| myIndentSpaces = 0; |
| setLineFeeds(0); |
| } |
| else if (!before && after) { |
| mySpaces = 1; |
| myIndentSpaces = 0; |
| } |
| } |
| } |
| |
| /** |
| * Tries to arrange {@link #getSpaces() spaces} property value to belong to |
| * [{@link SpacingImpl#getMinSpaces() min}; {@link SpacingImpl#getMaxSpaces()}] bounds defined by the given spacing object |
| * if {@link #getTotalSpaces() totalSpaces} property value lays out of the same bounds. |
| * <p/> |
| * The action is <b>not</b> performed if there are line feeds configured for the |
| * current {@link WhiteSpace} object ({@link #getSpaces()} != 0). |
| * |
| * @param spaceProperty spacing settings holder |
| */ |
| public void arrangeSpaces(final SpacingImpl spaceProperty) { |
| performModification(new Runnable() { |
| @Override |
| public void run() { |
| if (spaceProperty != null) { |
| if (getLineFeeds() == 0) { |
| if (spaceProperty.getMinSpaces() >= 0 && getTotalSpaces() < spaceProperty.getMinSpaces()) { |
| setSpaces(spaceProperty.getMinSpaces(), 0); |
| } |
| if (spaceProperty.getMaxSpaces() >= 0 && getTotalSpaces() > spaceProperty.getMaxSpaces()) { |
| setSpaces(spaceProperty.getMaxSpaces(), 0); |
| } |
| } |
| } |
| } |
| }); |
| } |
| |
| /** |
| * Tries to ensure that number of line feeds managed by the current {@link WhiteSpace} is consistent to the settings |
| * defined at the given spacing property. |
| * |
| * @param spaceProperty space settings holder |
| * @param formatProcessor format processor to use for space settings state refreshing |
| */ |
| public void arrangeLineFeeds(final SpacingImpl spaceProperty, final FormatProcessor formatProcessor) { |
| performModification(new Runnable() { |
| @Override |
| public void run() { |
| if (spaceProperty != null) { |
| spaceProperty.refresh(formatProcessor); |
| |
| if (spaceProperty.getMinLineFeeds() >= 0 && getLineFeeds() < spaceProperty.getMinLineFeeds()) { |
| setLineFeeds(spaceProperty.getMinLineFeeds()); |
| } |
| if (getLineFeeds() > 0) { |
| if (spaceProperty.getKeepBlankLines() > 0) { |
| if (getLineFeeds() >= spaceProperty.getKeepBlankLines() + 1) { |
| setLineFeeds(spaceProperty.getKeepBlankLines() + 1); |
| } |
| } |
| else { |
| if (getLineFeeds() > spaceProperty.getMinLineFeeds()) { |
| if (spaceProperty.shouldKeepLineFeeds()) { |
| setLineFeeds(Math.max(spaceProperty.getMinLineFeeds(), 1)); |
| } |
| else { |
| setLineFeeds(spaceProperty.getMinLineFeeds()); |
| if (getLineFeeds() == 0) mySpaces = 0; |
| } |
| } |
| } |
| if (getLineFeeds() == 1 && !spaceProperty.shouldKeepLineFeeds() && spaceProperty.getMinLineFeeds() == 0) { |
| setLineFeeds(0); |
| mySpaces = 0; |
| } |
| |
| if (getLineFeeds() > 0 && getLineFeeds() < spaceProperty.getPrefLineFeeds()) { |
| setLineFeeds(spaceProperty.getPrefLineFeeds()); |
| } |
| } |
| } else if (isFirst()) { |
| setLineFeeds(0); |
| mySpaces = 0; |
| } |
| } |
| }); |
| |
| } |
| |
| /** |
| * There is a possible case that particular indent info is applied to the code block that is not the first block on a line. |
| * E.g. we may want to align field name during <code>'align fields in columns'</code> processing: |
| * <pre> |
| * public class Test { |
| * private Object o; |
| * private int {@code <white space to align>}i; |
| * } |
| * </pre> |
| * We may not want to use tabulation characters then even if user configured |
| * {@link CommonCodeStyleSettings.IndentOptions#USE_TAB_CHARACTER their usage} because we can't be sure how many visual columns |
| * will be used for tab representation if there are non-white space symbols before it (IJ editor may use different number of columns |
| * for single tabulation symbol representation). |
| * <p/> |
| * Hence, we can ask current white space object to avoid using tabulation symbols. |
| * |
| * @param skip indicates if tabulations symbols usage should be suppressed |
| */ |
| public void setForceSkipTabulationsUsage(boolean skip) { |
| myForceSkipTabulationsUsage = skip; |
| } |
| |
| /** |
| * Allows to get information if target text document continuous 'white space' represented by the current object contained line feed |
| * symbol(s) initially or contains line feed(s) now. |
| * |
| * @return <code>true</code> if this object contained line feeds initially or contains them now; <code>false</code> otherwise |
| * @see #containsLineFeedsInitially() |
| */ |
| public boolean containsLineFeeds() { |
| return isIsFirstWhiteSpace() || getLineFeeds() > 0; |
| } |
| |
| public int getTotalSpaces() { |
| return mySpaces + myIndentSpaces; |
| } |
| |
| /** |
| * Tries to ensure that current {@link WhiteSpace} object contains at least one line feed. |
| */ |
| public void ensureLineFeed() { |
| performModification(new Runnable() { |
| @Override |
| public void run() { |
| if (!containsLineFeeds()) { |
| setLineFeeds(1); |
| mySpaces = 0; |
| } |
| } |
| }); |
| } |
| |
| public boolean isReadOnly() { |
| return isIsReadOnly() || (isIsSafe() && doesNotContainAnySpaces()); |
| } |
| |
| /** |
| * @param ws char sequence to check |
| * @return <code>true</code> if given char sequence is equal to the target document text identified by |
| * start/end offsets managed by the current {@link WhiteSpace} object; <code>false</code> otherwise |
| */ |
| public boolean equalsToString(CharSequence ws) { |
| if (myInitial == null) return ws.length() == 0; |
| return Comparing.equal(ws,myInitial,true); |
| } |
| |
| public void setIsSafe(final boolean value) { |
| setFlag(SAFE, value); |
| } |
| |
| private void setFlag(final int mask, final boolean value) { |
| if (value) { |
| myFlags |= mask; |
| } |
| else { |
| myFlags &= ~mask; |
| } |
| } |
| |
| private boolean getFlag(final int mask) { |
| return (myFlags & mask) != 0; |
| } |
| |
| private boolean isFirst() { |
| return isIsFirstWhiteSpace(); |
| } |
| |
| /** |
| * Allows to get information if current object contained line feed(s) initially. It's considered to contain them if |
| * line feeds were found at the target text document fragment after |
| * new {@link #append(int, FormattingDocumentModel, CommonCodeStyleSettings.IndentOptions) end offset appliance}. |
| * |
| * @return <code>true</code> if current object contained line feeds initially; <code>false</code> otherwise |
| */ |
| public boolean containsLineFeedsInitially() { |
| if (myInitial == null) return false; |
| return (myFlags & CONTAINS_LF_INITIALLY) != 0; |
| } |
| |
| /** |
| * Tries to ensure that number of line feeds and white spaces managed by the given {@link WhiteSpace} object is the |
| * same as the one defined by the given <code>'spacing'</code> object. |
| * <p/> |
| * This method may be considered a shortcut for calling {@link #arrangeLineFeeds(SpacingImpl, FormatProcessor)} and |
| * {@link #arrangeSpaces(SpacingImpl)}. |
| * |
| * @param spacing spacing settings holder |
| * @param formatProcessor format processor to use to refresh state of the given <code>'spacing'</code> object |
| */ |
| public void removeLineFeeds(final SpacingImpl spacing, final FormatProcessor formatProcessor) { |
| performModification(new Runnable() { |
| @Override |
| public void run() { |
| setLineFeeds(0); |
| mySpaces = 0; |
| myIndentSpaces = 0; |
| } |
| }); |
| arrangeLineFeeds(spacing, formatProcessor); |
| arrangeSpaces(spacing); |
| } |
| |
| public int getIndentOffset() { |
| return myIndentSpaces; |
| } |
| |
| /** |
| * Allows to get information about number of 'pure' white space symbols at the last line of continuous white space document |
| * text fragment represented by the current {@link WhiteSpace} object. |
| * <p/> |
| * <b>Note:</b> pay special attention to <code>'last line'</code> qualification here. Consider the following target continuous |
| * white space document fragment: |
| * <pre> |
| * ' ws<sub>11</sub>ws<sub>12</sub> |
| * 'ws<sub>21</sub>' |
| * </pre> |
| * <p/> |
| * Here <code>'ws<sub>nm</sub>'</code> is a m-th white space symbol at the n-th line. <code>'Spaces'</code> property of |
| * {@link WhiteSpace} object for such white-space text has a value not <b>2</b> or <b>3</b> but <b>1</b>. |
| * |
| * @return number of white spaces at the last line of target continuous white space text document fragment |
| */ |
| public int getSpaces() { |
| return mySpaces; |
| } |
| |
| public void setKeepFirstColumn(final boolean b) { |
| setFlag(KEEP_FIRST_COLUMN, b); |
| } |
| |
| public void setLineFeedsAreReadOnly() { |
| setLineFeedsAreReadOnly(true); |
| } |
| |
| public void setReadOnly(final boolean isReadOnly) { |
| setIsReadOnly(isReadOnly); |
| } |
| |
| public boolean isIsFirstWhiteSpace() { |
| return getFlag(FIRST); |
| } |
| |
| public boolean isIsSafe() { |
| return getFlag(SAFE); |
| } |
| |
| public boolean isKeepFirstColumn() { |
| return getFlag(KEEP_FIRST_COLUMN); |
| } |
| |
| public boolean isLineFeedsAreReadOnly() { |
| return getFlag(LINE_FEEDS_ARE_READ_ONLY); |
| } |
| |
| public void setLineFeedsAreReadOnly(final boolean lineFeedsAreReadOnly) { |
| setFlag(LINE_FEEDS_ARE_READ_ONLY, lineFeedsAreReadOnly); |
| } |
| |
| public boolean isIsReadOnly() { |
| return getFlag(READ_ONLY); |
| } |
| |
| public void setIsReadOnly(final boolean isReadOnly) { |
| setFlag(READ_ONLY, isReadOnly); |
| } |
| |
| public void setIsFirstWhiteSpace(final boolean isFirstWhiteSpace) { |
| setFlag(FIRST, isFirstWhiteSpace); |
| } |
| |
| public StringBuilder generateWhiteSpace(final CommonCodeStyleSettings.IndentOptions indentOptions, |
| final int offset, |
| final IndentInfo indent) { |
| final StringBuilder result = new StringBuilder(); |
| int currentOffset = getStartOffset(); |
| CharSequence[] lines = getInitialLines(); |
| int currentLine = 0; |
| for (int i = 0; i < lines.length - 1 && currentOffset + lines[i].length() <= offset; i++) { |
| result.append(lines[i]); |
| currentOffset += lines[i].length(); |
| result.append(LINE_FEED); |
| currentOffset++; |
| currentLine++; |
| if (currentOffset == offset) { |
| break; |
| } |
| |
| } |
| final String newIndentSpaces = indent.generateNewWhiteSpace(indentOptions); |
| result.append(newIndentSpaces); |
| appendNonWhitespaces(result, lines, currentLine); |
| if (currentLine + 1 < lines.length) { |
| result.append(LINE_FEED); |
| for (int i = currentLine + 1; i < lines.length - 1; i++) { |
| result.append(lines[i]); |
| result.append(LINE_FEED); |
| } |
| appendNonWhitespaces(result, lines, lines.length-1); |
| result.append(lines[lines.length - 1]); |
| |
| } |
| return result; |
| } |
| |
| @NotNull |
| public IndentInside getInitialLastLineIndent() { |
| return new IndentInside(myInitialLastLinesSpaces, myInitialLastLinesTabs); |
| } |
| |
| private static void appendNonWhitespaces(StringBuilder result, CharSequence[] lines, int currentLine) { |
| // It looks like regexp usage is too heavy here. |
| if (currentLine != lines.length && !lines[currentLine].toString().matches("\\s*")) { |
| result.append(lines[currentLine]); |
| } |
| } |
| |
| /** |
| * Shows target document text fragment identified by start/end offsets as a sequence of strings. |
| * <p/> |
| * <b>Note:</b> arrays usage here is considered to be a historical heritage. |
| * |
| * @return target document text fragment as a sequence of strings |
| */ |
| private CharSequence[] getInitialLines() { |
| if (myInitial == null) return new CharSequence[]{""}; |
| final ArrayList<CharSequence> result = new ArrayList<CharSequence>(); |
| StringBuilder currentLine = new StringBuilder(); |
| for (int i = 0; i < myInitial.length(); i++) { |
| final char c = myInitial.charAt(i); |
| if (c == LINE_FEED) { |
| result.add(currentLine); |
| currentLine = new StringBuilder(); |
| } |
| else { |
| currentLine.append(c); |
| } |
| } |
| result.add(currentLine); |
| return result.toArray(new CharSequence[result.size()]); |
| } |
| |
| /** |
| * Provides access to the information about indent white spaces at last line of continuous white space text document |
| * fragment represented by the current {@link WhiteSpace} object. |
| * <p/> |
| * <code>'Indent white space'</code> here is a white space representation of tabulation symbol. User may define that |
| * he or she wants to use particular number of white spaces instead of tabulation |
| * ({@link CommonCodeStyleSettings.IndentOptions#TAB_SIZE}). So, {@link WhiteSpace} object uses corresponding |
| * number of 'indent white spaces' for each tabulation symbol encountered at target continuous white space text document fragment. |
| * <p/> |
| * <b>Note:</b> pay special attention to <code>'last line'</code> qualification here. Consider the following target |
| * continuous white space document fragment: |
| * <pre> |
| * ' \t\t |
| * '\t' |
| * </pre> |
| * <p/> |
| * Let's consider that <code>'tab size'</code> is defined as four white spaces (default setting). |
| * <code>'IndentSpaces'</code> property of {@link WhiteSpace} object for such white-space text has a value not |
| * <b>8</b> or <b>12</b> but <b>4</b>, i.e. tabulation symbols from last line |
| * only are counted. |
| * |
| * @return number of indent spaces at the last line of target continuous white space text document fragment |
| */ |
| public int getIndentSpaces() { |
| return myIndentSpaces; |
| } |
| |
| public int getLength() { |
| return myEnd - myStart; |
| } |
| |
| /** |
| * Provides access to the line feed symbols number at continuous white space text document fragment represented |
| * by the current {@link WhiteSpace} object. |
| * |
| * @return line feed symbols number |
| */ |
| public final int getLineFeeds() { |
| return myFlags >>> LF_COUNT_SHIFT; |
| } |
| |
| public void setLineFeeds(final int lineFeeds) { |
| assert lineFeeds < MAX_LF_COUNT; |
| final int flags = myFlags; |
| myFlags &= ~0xFFFFFF80; // keep only seven lower bits, i.e. drop all line feeds registered before if any |
| myFlags |= (lineFeeds << LF_COUNT_SHIFT); |
| |
| assert getLineFeeds() == lineFeeds; |
| assert (flags & 0x7F) == (myFlags & 0x7F); |
| } |
| |
| public TextRange getTextRange() { |
| return new TextRange(myStart, myEnd); |
| } |
| |
| @Override |
| public String toString() { |
| return "WhiteSpace(" + myStart + "-" + myEnd + " spaces=" + mySpaces + " LFs=" + getLineFeeds() + ")"; |
| } |
| |
| private static class WhiteSpaceInfo { |
| |
| public final int spaces; |
| public final int indentSpaces; |
| public final int lineFeeds; |
| |
| WhiteSpaceInfo(int lineFeeds, int indentSpaces, int spaces) { |
| this.lineFeeds = lineFeeds; |
| this.indentSpaces = indentSpaces; |
| this.spaces = spaces; |
| } |
| } |
| } |
| |