From c2146c6d145c7ce3d5f6df207106cfa5589689f9 Mon Sep 17 00:00:00 2001 From: Anton Leherbauer Date: Fri, 2 Feb 2007 14:38:30 +0000 Subject: [PATCH] Fix for Bug 128361 - templates depending on line or word selection don't work --- .../internal/corext/template/c/CContext.java | 66 +++++------- .../corext/template/c/CContextType.java | 11 +- .../corext/template/c/CFormatter.java | 43 ++++---- .../corext/template/c/CommentContext.java | 49 +++------ .../corext/template/c/CommentContextType.java | 9 ++ .../template/c/TranslationUnitContext.java | 58 +++++++++- .../c/TranslationUnitContextType.java | 3 +- .../ui/text/template/TemplateEngine.java | 101 ++++++++++++++++-- .../templates/default-templates.xml | 10 +- 9 files changed, 239 insertions(+), 111 deletions(-) diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/corext/template/c/CContext.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/corext/template/c/CContext.java index c48bea5fce3..f87fae0d5af 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/corext/template/c/CContext.java +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/corext/template/c/CContext.java @@ -15,6 +15,7 @@ package org.eclipse.cdt.internal.corext.template.c; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.Position; import org.eclipse.jface.text.TextUtilities; import org.eclipse.jface.text.templates.Template; import org.eclipse.jface.text.templates.TemplateBuffer; @@ -47,81 +48,68 @@ public class CContext extends TranslationUnitContext { super(type, document, completionOffset, completionLength, translationUnit); } + /** + * Creates a C/C++ code template context. + * + * @param type the context type. + * @param document the document. + * @param completionPosition the completion position within the document + * @param translationUnit the translation unit (may be null). + */ + public CContext(TemplateContextType type, IDocument document, + Position completionPosition, ITranslationUnit translationUnit) { + super(type, document, completionPosition, translationUnit); + } + /* * @see DocumentTemplateContext#getStart() */ public int getStart() { + if (fIsManaged && getCompletionLength() > 0) + return super.getStart(); + try { IDocument document= getDocument(); - if (getCompletionLength() == 0) { - - int start= getCompletionOffset(); - while ((start != 0) && Character.isUnicodeIdentifierPart(document.getChar(start - 1))) - start--; - - if ((start != 0) && Character.isUnicodeIdentifierStart(document.getChar(start - 1))) - start--; - - return start; - - } - int start= getCompletionOffset(); int end= getCompletionOffset() + getCompletionLength(); - while (start != 0 && Character.isUnicodeIdentifierPart(document.getChar(start - 1))) { + while (start != 0 && !Character.isWhitespace(document.getChar(start - 1))) start--; - } - while (start != end && Character.isWhitespace(document.getChar(start))) { + while (start != end && Character.isWhitespace(document.getChar(start))) start++; - } - if (start == end) { - start= getCompletionOffset(); - } + if (start == end) + start= getCompletionOffset(); - return start; + return start; + } catch (BadLocationException e) { return super.getStart(); } - } public int getEnd() { - - if (getCompletionLength() == 0) + if (fIsManaged || getCompletionLength() == 0) return super.getEnd(); - try { + try { IDocument document= getDocument(); int start= getCompletionOffset(); int end= getCompletionOffset() + getCompletionLength(); - while (start != end && Character.isWhitespace(document.getChar(end - 1))) { + while (start != end && Character.isWhitespace(document.getChar(end - 1))) end--; - } return end; + } catch (BadLocationException e) { return super.getEnd(); } } - /* - * @see TemplateContext#canEvaluate(Template templates) - */ - public boolean canEvaluate(Template template) { - if (fForceEvaluation) - return true; - - String key= getKey(); - return template.matches(key, getContextType().getId()) - && key.length() != 0 && template.getName().toLowerCase().startsWith(key.toLowerCase()); - } - /* * @see TemplateContext#evaluate(Template) */ diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/corext/template/c/CContextType.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/corext/template/c/CContextType.java index cf4b625e33c..09513011c5d 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/corext/template/c/CContextType.java +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/corext/template/c/CContextType.java @@ -13,6 +13,7 @@ package org.eclipse.cdt.internal.corext.template.c; import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.Position; import org.eclipse.cdt.core.model.ITranslationUnit; @@ -27,7 +28,7 @@ public class CContextType extends TranslationUnitContextType { super(); } - /* (non-Javadoc) + /* * @see org.eclipse.cdt.internal.corext.template.c.TranslationUnitContextType#createContext(org.eclipse.jface.text.IDocument, int, int, org.eclipse.cdt.core.model.ITranslationUnit) */ public TranslationUnitContext createContext(IDocument document, int offset, @@ -35,4 +36,12 @@ public class CContextType extends TranslationUnitContextType { return new CContext(this, document, offset, length, translationUnit); } + /* + * @see org.eclipse.cdt.internal.corext.template.c.TranslationUnitContextType#createContext(org.eclipse.jface.text.IDocument, org.eclipse.jface.text.Position, org.eclipse.cdt.core.model.ITranslationUnit) + */ + public TranslationUnitContext createContext(IDocument document, + Position position, ITranslationUnit translationUnit) { + return new CContext(this, document, position, translationUnit); + } + } diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/corext/template/c/CFormatter.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/corext/template/c/CFormatter.java index 191ac988791..1177cc5bc86 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/corext/template/c/CFormatter.java +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/corext/template/c/CFormatter.java @@ -19,15 +19,6 @@ import java.util.Iterator; import java.util.List; import java.util.Map; -import org.eclipse.cdt.core.CCorePlugin; -import org.eclipse.cdt.core.formatter.CodeFormatter; -import org.eclipse.cdt.core.model.ICProject; - -import org.eclipse.cdt.internal.corext.util.CodeFormatterUtil; -import org.eclipse.cdt.internal.ui.editor.IndentUtil; -import org.eclipse.cdt.internal.ui.text.FastCPartitionScanner; -import org.eclipse.cdt.ui.text.ICPartitions; - import org.eclipse.core.runtime.Assert; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.BadPositionCategoryException; @@ -48,6 +39,16 @@ import org.eclipse.text.edits.RangeMarker; import org.eclipse.text.edits.ReplaceEdit; import org.eclipse.text.edits.TextEdit; +import org.eclipse.cdt.core.CCorePlugin; +import org.eclipse.cdt.core.formatter.CodeFormatter; +import org.eclipse.cdt.core.model.ICProject; +import org.eclipse.cdt.ui.text.ICPartitions; + +import org.eclipse.cdt.internal.corext.util.CodeFormatterUtil; + +import org.eclipse.cdt.internal.ui.editor.IndentUtil; +import org.eclipse.cdt.internal.ui.text.FastCPartitionScanner; + /** * A template editor using the C/C++ formatter to format a template buffer. @@ -94,7 +95,7 @@ public class CFormatter { internalFormat(document, context); convertLineDelimiters(document); - if (!isReplacedAreaEmpty(context)) + if (isReplacedAreaEmpty(context)) trimStart(document); tracker.updateBuffer(); @@ -142,19 +143,19 @@ public class CFormatter { } private boolean isReplacedAreaEmpty(TemplateContext context) { - // don't trim the buffer if the replacement area is empty - // case: surrounding empty lines with block if (context instanceof DocumentTemplateContext) { DocumentTemplateContext dtc= (DocumentTemplateContext) context; - if (dtc.getStart() == dtc.getCompletionOffset()) - try { - if (dtc.getDocument().get(dtc.getStart(), dtc.getEnd() - dtc.getStart()).trim().length() == 0) - return true; - } catch (BadLocationException x) { - // ignore - this may happen when the document was modified after the initial invocation, and the - // context does not track the changes properly - don't trim in that case + if (dtc.getCompletionLength() == 0) { + return true; + } + try { + if (dtc.getDocument().get(dtc.getStart(), dtc.getEnd() - dtc.getStart()).trim().length() == 0) return true; - } + } catch (BadLocationException x) { + // ignore - this may happen when the document was modified after the initial invocation, and the + // context does not track the changes properly - don't trim in that case + return false; + } } return false; } @@ -319,7 +320,7 @@ public class CFormatter { private boolean isWhitespaceVariable(String value) { int length= value.length(); - return length == 0 || Character.isWhitespace(value.charAt(0)) || Character.isWhitespace(value.charAt(length - 1)); + return length == 0 || value.trim().length() == 0; } private void removeRangeMarkers(List positions, IDocument document, TemplateVariable[] variables) throws MalformedTreeException, BadLocationException, BadPositionCategoryException { diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/corext/template/c/CommentContext.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/corext/template/c/CommentContext.java index e53300dab81..bb38305aa9d 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/corext/template/c/CommentContext.java +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/corext/template/c/CommentContext.java @@ -13,6 +13,7 @@ package org.eclipse.cdt.internal.corext.template.c; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.Position; import org.eclipse.jface.text.TextUtilities; import org.eclipse.jface.text.templates.Template; import org.eclipse.jface.text.templates.TemplateBuffer; @@ -44,25 +45,24 @@ public class CommentContext extends TranslationUnitContext { super(type, document, completionOffset, completionLength, translationUnit); } - /* - * @see TemplateContext#canEvaluate(Template templates) + /** + * Creates a comment template context. + * + * @param type the context type. + * @param document the document. + * @param completionPosition the completion position within the document + * @param translationUnit the translation unit (may be null). */ - public boolean canEvaluate(Template template) { - String key= getKey(); - - if (fForceEvaluation) - return true; - - return - template.matches(key, getContextType().getId()) && - (key.length() != 0) && template.getName().toLowerCase().startsWith(key.toLowerCase()); + public CommentContext(TemplateContextType type, IDocument document, + Position completionPosition, ITranslationUnit translationUnit) { + super(type, document, completionPosition, translationUnit); } /* * @see DocumentTemplateContext#getStart() */ public int getStart() { - if (/*fIsManaged &&*/ getCompletionLength() > 0) + if (fIsManaged && getCompletionLength() > 0) return super.getStart(); try { @@ -105,8 +105,7 @@ public class CommentContext extends TranslationUnitContext { * @see org.eclipse.jdt.internal.corext.template.DocumentTemplateContext#getEnd() */ public int getEnd() { - - if (/*fIsManaged ||*/ getCompletionLength() == 0) + if (fIsManaged || getCompletionLength() == 0) return super.getEnd(); try { @@ -125,28 +124,6 @@ public class CommentContext extends TranslationUnitContext { } } - /* - * @see org.eclipse.jdt.internal.corext.template.DocumentTemplateContext#getKey() - */ - public String getKey() { - - if (getCompletionLength() == 0) - return super.getKey(); - - try { - IDocument document= getDocument(); - - int start= getStart(); - int end= getCompletionOffset(); - return start <= end - ? document.get(start, end - start) - : ""; //$NON-NLS-1$ - - } catch (BadLocationException e) { - return super.getKey(); - } - } - /* * @see TemplateContext#evaluate(Template) */ diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/corext/template/c/CommentContextType.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/corext/template/c/CommentContextType.java index eba784e346e..2bdde5bca40 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/corext/template/c/CommentContextType.java +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/corext/template/c/CommentContextType.java @@ -12,6 +12,7 @@ package org.eclipse.cdt.internal.corext.template.c; import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.Position; import org.eclipse.cdt.core.model.ITranslationUnit; @@ -39,4 +40,12 @@ public class CommentContextType extends TranslationUnitContextType { return new CommentContext(this, document, offset, length, translationUnit); } + /* + * @see org.eclipse.cdt.internal.corext.template.c.TranslationUnitContextType#createContext(org.eclipse.jface.text.IDocument, org.eclipse.jface.text.Position, org.eclipse.cdt.core.model.ITranslationUnit) + */ + public TranslationUnitContext createContext(IDocument document, + Position position, ITranslationUnit translationUnit) { + return new CommentContext(this, document, position, translationUnit); + } + } diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/corext/template/c/TranslationUnitContext.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/corext/template/c/TranslationUnitContext.java index a996b83f8d8..6c8ea8b70c0 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/corext/template/c/TranslationUnitContext.java +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/corext/template/c/TranslationUnitContext.java @@ -16,7 +16,9 @@ package org.eclipse.cdt.internal.corext.template.c; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.Position; import org.eclipse.jface.text.templates.DocumentTemplateContext; +import org.eclipse.jface.text.templates.Template; import org.eclipse.jface.text.templates.TemplateContextType; import org.eclipse.cdt.core.model.CModelException; @@ -37,6 +39,8 @@ public abstract class TranslationUnitContext extends DocumentTemplateContext { private final ITranslationUnit fTranslationUnit; /** A flag to force evaluation in head-less mode. */ protected boolean fForceEvaluation; + /** true if the context has a managed (i.e. added to the document) position, false otherwise. */ + protected final boolean fIsManaged; /** * Creates a translation unit context. @@ -48,10 +52,60 @@ public abstract class TranslationUnitContext extends DocumentTemplateContext { * @param translationUnit the translation unit represented by the document */ protected TranslationUnitContext(TemplateContextType type, IDocument document, int completionOffset, - int completionLength, ITranslationUnit translationUnit) - { + int completionLength, ITranslationUnit translationUnit) { super(type, document, completionOffset, completionLength); fTranslationUnit= translationUnit; + fIsManaged= false; + } + + /** + * Creates a translation unit context. + * + * @param type the context type + * @param document the document + * @param completionPosition the completion position within the document + * @param translationUnit the translation unit represented by the document + */ + protected TranslationUnitContext(TemplateContextType type, IDocument document, + Position completionPosition, ITranslationUnit translationUnit) { + super(type, document, completionPosition); + fTranslationUnit= translationUnit; + fIsManaged= true; + } + + /* + * @see org.eclipse.jface.text.templates.DocumentTemplateContext#canEvaluate(org.eclipse.jface.text.templates.Template) + */ + public boolean canEvaluate(Template template) { + if (fForceEvaluation) + return true; + + String key= getKey(); + return template.matches(key, getContextType().getId()) + && key.length() != 0 && template.getName().startsWith(key); + } + + + + /* + * @see org.eclipse.jdt.internal.corext.template.DocumentTemplateContext#getKey() + */ + public String getKey() { + if (getCompletionLength() == 0) + return super.getKey(); + + try { + IDocument document= getDocument(); + + int start= getStart(); + int end= getCompletionOffset(); + return start <= end + ? document.get(start, end - start) + : ""; //$NON-NLS-1$ + + } catch (BadLocationException e) { + return super.getKey(); + } } /** diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/corext/template/c/TranslationUnitContextType.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/corext/template/c/TranslationUnitContextType.java index 9c2e724c9d8..064279e8235 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/corext/template/c/TranslationUnitContextType.java +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/corext/template/c/TranslationUnitContextType.java @@ -17,6 +17,7 @@ import org.eclipse.cdt.core.model.ICElement; import org.eclipse.cdt.core.model.IFunctionDeclaration; import org.eclipse.cdt.core.model.ITranslationUnit; import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.Position; import org.eclipse.jface.text.templates.GlobalTemplateVariables; import org.eclipse.jface.text.templates.TemplateContext; import org.eclipse.jface.text.templates.TemplateContextType; @@ -167,7 +168,7 @@ public abstract class TranslationUnitContextType extends TemplateContextType { } public abstract TranslationUnitContext createContext(IDocument document, int offset, int length, ITranslationUnit translationUnit); - + public abstract TranslationUnitContext createContext(IDocument document, Position position, ITranslationUnit translationUnit); } diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/template/TemplateEngine.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/template/TemplateEngine.java index 2381e0a76fd..910b33cb6b8 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/template/TemplateEngine.java +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/template/TemplateEngine.java @@ -13,15 +13,22 @@ package org.eclipse.cdt.internal.ui.text.template; import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; import org.eclipse.core.runtime.Assert; +import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IInformationControl; import org.eclipse.jface.text.IInformationControlCreator; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.Position; import org.eclipse.jface.text.Region; +import org.eclipse.jface.text.templates.GlobalTemplateVariables; import org.eclipse.jface.text.templates.Template; import org.eclipse.jface.text.templates.TemplateContext; import org.eclipse.jface.text.templates.TemplateContextType; @@ -44,8 +51,15 @@ import org.eclipse.cdt.internal.ui.text.c.hover.SourceViewerInformationControl; public class TemplateEngine { + private static final String $_LINE_SELECTION= "${" + GlobalTemplateVariables.LineSelection.NAME + "}"; //$NON-NLS-1$ //$NON-NLS-2$ + private static final String $_WORD_SELECTION= "${" + GlobalTemplateVariables.WordSelection.NAME + "}"; //$NON-NLS-1$ //$NON-NLS-2$ + + /** The context type. */ private TemplateContextType fContextType; + /** The result proposals. */ private ArrayList fProposals= new ArrayList(); + /** Positions created on the key documents to remove in reset. */ + private final Map fPositions= new HashMap(); public class CTemplateProposal extends TemplateProposal implements ICCompletionProposal { @@ -101,6 +115,13 @@ public class TemplateEngine { */ public void reset() { fProposals.clear(); + for (Iterator it= fPositions.entrySet().iterator(); it.hasNext();) { + Entry entry= (Entry) it.next(); + IDocument doc= (IDocument) entry.getKey(); + Position position= (Position) entry.getValue(); + doc.removePosition(position); + } + fPositions.clear(); } /** @@ -120,23 +141,91 @@ public class TemplateEngine { */ public void complete(ITextViewer viewer, int completionPosition, ITranslationUnit translationUnit) { - IDocument document= viewer.getDocument(); - if (!(fContextType instanceof TranslationUnitContextType)) return; + IDocument document= viewer.getDocument(); Point selection= viewer.getSelectedRange(); + boolean multipleLinesSelected= areMultipleLinesSelected(viewer); + if (multipleLinesSelected) { + // adjust line selection to start at column 1 and end at line delimiter + try { + IRegion startLine = document.getLineInformationOfOffset(selection.x); + IRegion endLine = document.getLineInformationOfOffset(selection.x + selection.y - 1); + completionPosition= selection.x= startLine.getOffset(); + selection.y= endLine.getOffset() + endLine.getLength() - startLine.getOffset(); + } catch (BadLocationException exc) { + } + } + Position position= new Position(completionPosition, selection.y); - TranslationUnitContext context= ((TranslationUnitContextType) fContextType).createContext(document, completionPosition, selection.y, translationUnit); + // remember selected text + String selectedText= null; + if (selection.y != 0) { + try { + selectedText= document.get(selection.x, selection.y); + document.addPosition(position); + fPositions.put(document, position); + } catch (BadLocationException e) {} + } + + TranslationUnitContext context= ((TranslationUnitContextType) fContextType).createContext(document, position, translationUnit); + context.setVariable("selection", selectedText); //$NON-NLS-1$ int start= context.getStart(); int end= context.getEnd(); IRegion region= new Region(start, end - start); Template[] templates= CUIPlugin.getDefault().getTemplateStore().getTemplates(); - for (int i= 0; i != templates.length; i++) - if (context.canEvaluate(templates[i])) - fProposals.add(new CTemplateProposal(templates[i], context, region, CPluginImages.get(CPluginImages.IMG_OBJS_TEMPLATE))); + + if (selection.y == 0) { + for (int i= 0; i != templates.length; i++) + if (context.canEvaluate(templates[i])) + fProposals.add(new CTemplateProposal(templates[i], context, region, CPluginImages.get(CPluginImages.IMG_OBJS_TEMPLATE))); + + } else { + + if (multipleLinesSelected || context.getKey().length() == 0) + context.setForceEvaluation(true); + + for (int i= 0; i != templates.length; i++) { + Template template= templates[i]; + if (context.canEvaluate(template) && + template.getContextTypeId().equals(context.getContextType().getId()) && + (!multipleLinesSelected && template.getPattern().indexOf($_WORD_SELECTION) != -1 || (multipleLinesSelected && template.getPattern().indexOf($_LINE_SELECTION) != -1))) + { + fProposals.add(new CTemplateProposal(templates[i], context, region, CPluginImages.get(CPluginImages.IMG_OBJS_TEMPLATE))); + } + } + } + } + /** + * Returns true if one line is completely selected or if multiple lines are selected. + * Being completely selected means that all characters except the new line characters are + * selected. + * + * @return true if one or multiple lines are selected + */ + private boolean areMultipleLinesSelected(ITextViewer viewer) { + if (viewer == null) + return false; + + Point s= viewer.getSelectedRange(); + if (s.y == 0) + return false; + + try { + + IDocument document= viewer.getDocument(); + int startLine= document.getLineOfOffset(s.x); + int endLine= document.getLineOfOffset(s.x + s.y); + IRegion line= document.getLineInformation(startLine); + return startLine != endLine || (s.x == line.getOffset() && s.y == line.getLength()); + + } catch (BadLocationException x) { + return false; + } + } } diff --git a/core/org.eclipse.cdt.ui/templates/default-templates.xml b/core/org.eclipse.cdt.ui/templates/default-templates.xml index 1250b2a6dec..6ad497396ea 100644 --- a/core/org.eclipse.cdt.ui/templates/default-templates.xml +++ b/core/org.eclipse.cdt.ui/templates/default-templates.xml @@ -21,14 +21,14 @@