mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IDEA-69506 Code Wrapping Creates Errors In XML
1. Defined basic class for PSI-aware line wrapping strategy; 2. Created markup-specific line wrapping strategy; 3. Corresponding test is added;
This commit is contained in:
+6
@@ -53,4 +53,10 @@ public class LanguageLineWrapPositionStrategy extends LanguageExtension<LineWrap
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public LineWrapPositionStrategy getDefaultImplementation() {
|
||||
return super.getDefaultImplementation();
|
||||
}
|
||||
}
|
||||
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.openapi.editor;
|
||||
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.psi.tree.IElementType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* {@link LineWrapPositionStrategy} implementation that uses
|
||||
* {@link LanguageLineWrapPositionStrategy#getDefaultImplementation() default line wrap strategy} but restricts its scope
|
||||
* by {@link #PsiAwareDefaultLineWrapPositionStrategy(IElementType...) target tokens/elements}.
|
||||
*
|
||||
* @author Denis Zhdanov
|
||||
* @since 5/12/11 12:50 PM
|
||||
*/
|
||||
public class PsiAwareDefaultLineWrapPositionStrategy extends PsiAwareLineWrapPositionStrategy {
|
||||
|
||||
public PsiAwareDefaultLineWrapPositionStrategy(@NotNull IElementType ... enabledTypes) {
|
||||
super(enabledTypes);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int doCalculateWrapPosition(@NotNull Document document,
|
||||
@Nullable Project project,
|
||||
int startOffset,
|
||||
int endOffset,
|
||||
int maxPreferredOffset,
|
||||
boolean allowToBeyondMaxPreferredOffset)
|
||||
{
|
||||
LineWrapPositionStrategy implementation = LanguageLineWrapPositionStrategy.INSTANCE.getDefaultImplementation();
|
||||
return implementation.calculateWrapPosition(document, project, startOffset, endOffset, maxPreferredOffset,
|
||||
allowToBeyondMaxPreferredOffset);
|
||||
}
|
||||
}
|
||||
+170
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* 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.openapi.editor;
|
||||
|
||||
import com.intellij.lang.ASTNode;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.psi.PsiDocumentManager;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.tree.IElementType;
|
||||
import com.intellij.psi.tree.TokenSet;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Base super-class for {@link LineWrapPositionStrategy} implementations that want to restrict wrap positions
|
||||
* only for particular elements/tokens (e.g. we may want to avoid line wrap in the middle of xml tag name etc).
|
||||
*
|
||||
* @author Denis Zhdanov
|
||||
* @since 5/12/11 12:30 PM
|
||||
*/
|
||||
public abstract class PsiAwareLineWrapPositionStrategy implements LineWrapPositionStrategy {
|
||||
|
||||
private static final Logger LOG = Logger.getInstance("#" + PsiAwareLineWrapPositionStrategy.class.getName());
|
||||
|
||||
private final TokenSet myEnabledTypes;
|
||||
|
||||
/**
|
||||
* Creates new <code>PsiAwareLineWrapPositionStrategy</code> 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
|
||||
* <code>(startOffset; maxPreferredOffset]</code> interval. However, it's allowed
|
||||
* to return value from <code>(maxPreferredOffset; endOffset]</code> interval
|
||||
* unless <code>'allowToBeyondMaxPreferredOffset'</code> if <code>'false'</code>
|
||||
* @param allowToBeyondMaxPreferredOffset indicates if it's allowed to return value from
|
||||
* <code>(maxPreferredOffset; endOffset]</code> interval in case of inability to
|
||||
* find appropriate offset from <code>(startOffset; maxPreferredOffset]</code> interval
|
||||
* @return offset from <code>(startOffset; endOffset]</code> interval where
|
||||
* target line should be wrapped OR <code>-1</code> 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 <code>true</code> if wrapping at the text range defined by the given element is allowed;
|
||||
* <code>false</code> 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;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -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;
|
||||
}
|
||||
|
||||
+2
-1
@@ -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;
|
||||
|
||||
+3
@@ -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,
|
||||
|
||||
@@ -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 <code>-1</code> 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
|
||||
);
|
||||
}
|
||||
|
||||
+4
-1
@@ -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);
|
||||
}
|
||||
|
||||
+1
-1
@@ -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) {
|
||||
|
||||
@@ -150,6 +150,9 @@
|
||||
<lang.formatter language="XML" implementationClass="com.intellij.lang.xml.XmlFormattingModelBuilder"/>
|
||||
<lang.formatter language="HTML" implementationClass="com.intellij.lang.html.HtmlFormattingModelBuilder"/>
|
||||
<lang.formatter language="XHTML" implementationClass="com.intellij.lang.xhtml.XhtmlFormattingModelBuilder"/>
|
||||
<lang.lineWrapStrategy language="XML" implementationClass="com.intellij.psi.formatter.MarkupLineWrapPositionStrategy"/>
|
||||
<lang.lineWrapStrategy language="HTML" implementationClass="com.intellij.psi.formatter.MarkupLineWrapPositionStrategy"/>
|
||||
<lang.lineWrapStrategy language="XHTML" implementationClass="com.intellij.psi.formatter.MarkupLineWrapPositionStrategy"/>
|
||||
|
||||
<lang.documentationProvider language="XML" implementationClass="com.intellij.xml.util.documentation.XmlDocumentationProvider"
|
||||
order="last"/>
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user