diff --git a/platform/lang-api/src/com/intellij/openapi/editor/LanguageLineWrapPositionStrategy.java b/platform/lang-api/src/com/intellij/openapi/editor/LanguageLineWrapPositionStrategy.java index 5a764d64fd28..c60e9996bb53 100644 --- a/platform/lang-api/src/com/intellij/openapi/editor/LanguageLineWrapPositionStrategy.java +++ b/platform/lang-api/src/com/intellij/openapi/editor/LanguageLineWrapPositionStrategy.java @@ -53,4 +53,10 @@ public class LanguageLineWrapPositionStrategy extends LanguageExtensionPsiAwareLineWrapPositionStrategy object. + * + * @param enabledTypes target element/token types where line wrapping is allowed + */ + public PsiAwareLineWrapPositionStrategy(@NotNull IElementType ... enabledTypes) { + myEnabledTypes = TokenSet.create(enabledTypes); + if (enabledTypes.length <= 0) { + LOG.warn(String.format("%s instance is created with empty token/element types. That will lead to inability to perform line wrap", + getClass().getName())); + } + } + + @Override + public int calculateWrapPosition(@NotNull Document document, + @Nullable Project project, + int startOffset, + int endOffset, + int maxPreferredOffset, + boolean allowToBeyondMaxPreferredOffset) { + if (project == null) { + return -1; + } + + PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project); + if (documentManager == null) { + return -1; + } + + PsiFile psiFile = documentManager.getPsiFile(document); + if (psiFile == null) { + return -1; + } + + PsiElement element = psiFile.findElementAt(maxPreferredOffset); + if (element == null) { + return -1; + } + + for (; element != null && element.getTextRange().getEndOffset() > startOffset; element = getPrevious(element)) { + if (allowToWrapInside(element)) { + TextRange textRange = element.getTextRange(); + int start = textRange.getStartOffset(); + int end = textRange.getEndOffset(); + int result = doCalculateWrapPosition(document, project, start, end, end, false); + if (result >= 0) { + return result; + } + + // Assume that it's possible to wrap on token boundary (makes sense at least for the tokens that occupy one symbol only). + if (end <= maxPreferredOffset) { + return end; + } + + if (start > startOffset) { + return start; + } + } + } + return -1; + } + + /** + * Serves for the same purposes as {@link #calculateWrapPosition(Document, Project, int, int, int, boolean)} but ensures that given + * offsets target {@link #PsiAwareLineWrapPositionStrategy(IElementType...) enabled token/element types}. + * + * @param document target document which text is being processed + * @param project target project + * @param startOffset start offset to use with the given text holder (inclusive) + * @param endOffset end offset to use with the given text holder (exclusive) + * @param maxPreferredOffset this method is expected to do its best to return offset that belongs to + * (startOffset; maxPreferredOffset] interval. However, it's allowed + * to return value from (maxPreferredOffset; endOffset] interval + * unless 'allowToBeyondMaxPreferredOffset' if 'false' + * @param allowToBeyondMaxPreferredOffset indicates if it's allowed to return value from + * (maxPreferredOffset; endOffset] interval in case of inability to + * find appropriate offset from (startOffset; maxPreferredOffset] interval + * @return offset from (startOffset; endOffset] interval where + * target line should be wrapped OR -1 if no wrapping should be performed + */ + protected abstract int doCalculateWrapPosition( + @NotNull Document document, @Nullable Project project, int startOffset, int endOffset, int maxPreferredOffset, + boolean allowToBeyondMaxPreferredOffset + ); + + /** + * Allows to check if line wrap at the text range defined by the given element is allowed. + * + * @param element element that defines target text range + * @return true if wrapping at the text range defined by the given element is allowed; + * false otherwise + */ + private boolean allowToWrapInside(@NotNull PsiElement element) { + TextRange textRange = element.getTextRange(); + for (PsiElement parent = element; parent != null && parent.getTextRange().equals(textRange); parent = parent.getParent()) { + ASTNode parentNode = parent.getNode(); + if (parentNode != null && myEnabledTypes.contains(parentNode.getElementType())) { + return true; + } + } + return false; + } + + @Nullable + private static PsiElement getPrevious(@NotNull PsiElement element) { + PsiElement result = element.getPrevSibling(); + if (result != null) { + return result; + } + + PsiElement parent = element.getParent(); + if (parent == null) { + return null; + } + + PsiElement parentSibling = null; + for (; parent != null && parentSibling == null; parent = parent.getParent()) { + parentSibling = parent.getPrevSibling(); + } + + if (parentSibling == null) { + return null; + } + + result = parentSibling.getLastChild(); + return result == null ? parentSibling : result; + } +} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/AutoHardWrapHandler.java b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/AutoHardWrapHandler.java index 93380dba8fbe..656992640479 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/AutoHardWrapHandler.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/AutoHardWrapHandler.java @@ -150,7 +150,7 @@ public class AutoHardWrapHandler { new VisualPosition(caretModel.getVisualPosition().line, margin - FormatConstants.RESERVED_LINE_WRAP_WIDTH_IN_COLUMNS) )); - int wrapOffset = strategy.calculateWrapPosition(document, startOffset, endOffset, maxPreferredOffset, true); + int wrapOffset = strategy.calculateWrapPosition(document, project, startOffset, endOffset, maxPreferredOffset, true); if (wrapOffset < 0) { return; } diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/CodeFormatterFacade.java b/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/CodeFormatterFacade.java index 74d194ad300b..c37d5cf16b4a 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/CodeFormatterFacade.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/CodeFormatterFacade.java @@ -370,7 +370,8 @@ public class CodeFormatterFacade { // We know that current line exceeds right margin if control flow reaches this place, so, wrap it. int wrapOffset = strategy.calculateWrapPosition( - document, Math.max(startLineOffset, startOffsetToUse), Math.min(endLineOffset, endOffsetToUse), preferredWrapPosition, false + document, editor.getProject(), Math.max(startLineOffset, startOffsetToUse), Math.min(endLineOffset, endOffsetToUse), + preferredWrapPosition, false ); if (wrapOffset < 0) { continue; diff --git a/platform/platform-api/src/com/intellij/openapi/editor/GenericLineWrapPositionStrategy.java b/platform/platform-api/src/com/intellij/openapi/editor/GenericLineWrapPositionStrategy.java index 034752813a19..93289b0caddd 100644 --- a/platform/platform-api/src/com/intellij/openapi/editor/GenericLineWrapPositionStrategy.java +++ b/platform/platform-api/src/com/intellij/openapi/editor/GenericLineWrapPositionStrategy.java @@ -15,8 +15,10 @@ */ package com.intellij.openapi.editor; +import com.intellij.openapi.project.Project; import gnu.trove.TIntObjectHashMap; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.Arrays; @@ -42,6 +44,7 @@ public class GenericLineWrapPositionStrategy implements LineWrapPositionStrategy @Override public int calculateWrapPosition(@NotNull Document document, + @Nullable Project project, int startOffset, int endOffset, int maxPreferredOffset, diff --git a/platform/platform-api/src/com/intellij/openapi/editor/LineWrapPositionStrategy.java b/platform/platform-api/src/com/intellij/openapi/editor/LineWrapPositionStrategy.java index 5463c323c295..958db895b793 100644 --- a/platform/platform-api/src/com/intellij/openapi/editor/LineWrapPositionStrategy.java +++ b/platform/platform-api/src/com/intellij/openapi/editor/LineWrapPositionStrategy.java @@ -15,7 +15,9 @@ */ package com.intellij.openapi.editor; +import com.intellij.openapi.project.Project; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; /** * Defines contract for the strategy that calculates the best place to apply wrap to particular line (sequence of characters). @@ -33,6 +35,7 @@ public interface LineWrapPositionStrategy { * Allows to calculate the most appropriate position to wrap target line. * * @param document target document which text is being processed + * @param project target project * @param startOffset start offset to use with the given text holder (inclusive) * @param endOffset end offset to use with the given text holder (exclusive) * @param maxPreferredOffset this method is expected to do its best to return offset that belongs to @@ -46,7 +49,7 @@ public interface LineWrapPositionStrategy { * target line should be wrapped OR -1 if no wrapping should be performed */ int calculateWrapPosition( - @NotNull Document document, int startOffset, int endOffset, int maxPreferredOffset, + @NotNull Document document, @Nullable Project project, int startOffset, int endOffset, int maxPreferredOffset, boolean allowToBeyondMaxPreferredOffset ); } diff --git a/platform/platform-api/testSrc/com/intellij/openapi/editor/DefaultLineWrapPositionStrategyTest.java b/platform/platform-api/testSrc/com/intellij/openapi/editor/DefaultLineWrapPositionStrategyTest.java index 5039210ba0e0..8d5ba9fa4e19 100644 --- a/platform/platform-api/testSrc/com/intellij/openapi/editor/DefaultLineWrapPositionStrategyTest.java +++ b/platform/platform-api/testSrc/com/intellij/openapi/editor/DefaultLineWrapPositionStrategyTest.java @@ -15,6 +15,7 @@ */ package com.intellij.openapi.editor; +import com.intellij.openapi.project.Project; import org.jetbrains.annotations.NotNull; import org.jmock.Expectations; import org.jmock.Mockery; @@ -36,6 +37,7 @@ public class DefaultLineWrapPositionStrategyTest { private Mockery myMockery; private DefaultLineWrapPositionStrategy myStrategy; + private Project myProject; @Before public void setUp() { @@ -44,6 +46,7 @@ public class DefaultLineWrapPositionStrategyTest { myMockery = new JUnit4Mockery() {{ setImposteriser(ClassImposteriser.INSTANCE); }}; + myProject = myMockery.mock(Project.class); } @Test @@ -82,7 +85,7 @@ public class DefaultLineWrapPositionStrategyTest { final Context context = new Context(document); context.init(); int actual = myStrategy.calculateWrapPosition( - createMockDocument(context.document), 0, context.document.length(), context.edgeIndex, allowToBeyondMaxPreferredOffset + createMockDocument(context.document), myProject, 0, context.document.length(), context.edgeIndex, allowToBeyondMaxPreferredOffset ); assertSame(context.wrapIndex, actual); } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/SoftWrapApplianceManager.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/SoftWrapApplianceManager.java index 018bd8e86f97..c5b13189cd6f 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/SoftWrapApplianceManager.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/SoftWrapApplianceManager.java @@ -519,7 +519,7 @@ public class SoftWrapApplianceManager implements SoftWrapFoldingListener, Docume myLineWrapPositionStrategy = LanguageLineWrapPositionStrategy.INSTANCE.forEditor(myEditor); } - softWrapOffset = myLineWrapPositionStrategy.calculateWrapPosition(document, minOffset, maxOffset, preferredOffset, true); + softWrapOffset = myLineWrapPositionStrategy.calculateWrapPosition(document, myEditor.getProject(), minOffset, maxOffset, preferredOffset, true); } if (softWrapOffset >= lineData.endLineOffset || softWrapOffset < 0) { diff --git a/platform/platform-resources/src/META-INF/XmlPlugin.xml b/platform/platform-resources/src/META-INF/XmlPlugin.xml index b5ee440d7542..0b7fc46cdafc 100644 --- a/platform/platform-resources/src/META-INF/XmlPlugin.xml +++ b/platform/platform-resources/src/META-INF/XmlPlugin.xml @@ -150,6 +150,9 @@ + + + diff --git a/platform/testFramework/src/com/intellij/testFramework/TestFileType.java b/platform/testFramework/src/com/intellij/testFramework/TestFileType.java index 42f1174cc764..9b4c58cdffcf 100644 --- a/platform/testFramework/src/com/intellij/testFramework/TestFileType.java +++ b/platform/testFramework/src/com/intellij/testFramework/TestFileType.java @@ -23,7 +23,7 @@ package com.intellij.testFramework; */ public enum TestFileType { - JAVA("java"), SQL("sql"), TEXT("txt"); + JAVA("java"), SQL("sql"), TEXT("txt"), XML("xml"), HTML("html"), XHTML("xhtml"); private final String myExtension; diff --git a/xml/impl/src/com/intellij/psi/formatter/MarkupLineWrapPositionStrategy.java b/xml/impl/src/com/intellij/psi/formatter/MarkupLineWrapPositionStrategy.java new file mode 100644 index 000000000000..1f3c21bc9960 --- /dev/null +++ b/xml/impl/src/com/intellij/psi/formatter/MarkupLineWrapPositionStrategy.java @@ -0,0 +1,35 @@ +/* + * Copyright 2000-2011 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.psi.formatter; + +import com.intellij.openapi.editor.LineWrapPositionStrategy; +import com.intellij.openapi.editor.PsiAwareDefaultLineWrapPositionStrategy; +import com.intellij.psi.TokenType; +import com.intellij.psi.xml.XmlElementType; +import com.intellij.psi.xml.XmlTokenType; + +/** + * {@link LineWrapPositionStrategy} for markup languages like XML, HTML etc. + * + * @author Denis Zhdanov + * @since 5/11/11 7:42 PM + */ +public class MarkupLineWrapPositionStrategy extends PsiAwareDefaultLineWrapPositionStrategy { + + public MarkupLineWrapPositionStrategy() { + super(XmlElementType.XML_TEXT, TokenType.WHITE_SPACE); + } +}