Skip to content

Commit fedaeb5

Browse files
committed
Implemented a better version of "surround with"
1 parent 3861878 commit fedaeb5

10 files changed

Lines changed: 307 additions & 58 deletions

resources/META-INF/plugin.xml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
<id>de.halirutan.mathematica</id>
2424
<name>Mathematica Support</name>
2525
<category>Custom Language</category>
26-
<version>2.0.3</version>
26+
<version>2.0.30</version>
2727
<idea-version since-build="163"/>
2828
<vendor email="patrick@halirutan.de" url="http://mathematicaplugin.halirutan.de">Patrick Scheibe</vendor>
2929
<depends>com.intellij.modules.lang</depends>
@@ -121,7 +121,8 @@
121121

122122
<lang.surroundDescriptor language="Mathematica"
123123
implementationClass="de.halirutan.mathematica.codeinsight.surround.MathematicaSurroundDescriptor"/>
124-
124+
<codeInsight.surroundWithRangeAdjuster
125+
implementation="de.halirutan.mathematica.codeinsight.surround.MathematicaSurroundWithRangeAdjuster"/>
125126
<liveTemplateContext
126127
implementation="de.halirutan.mathematica.codeinsight.livetemplates.MathematicaTemplateContextType"/>
127128
<defaultLiveTemplatesProvider
@@ -133,8 +134,8 @@
133134
implementation="de.halirutan.mathematica.codeinsight.editoractions.wordselection.MathematicaListSelectioner"/>
134135

135136
<!--<basicWordSelectionFilter-->
136-
<!--implementation="de.halirutan.mathematica.codeinsight.editoractions.wordselection.BasicExpressionSelectionFilter"/>-->
137-
137+
<!--implementation="de.halirutan.mathematica.codeinsight.editoractions.wordselection.BasicExpressionSelectionFilter"/>-->
138+
138139
<lang.refactoringSupport language="Mathematica"
139140
implementationClass="de.halirutan.mathematica.refactoring.MathematicaRefactoringSupport"/>
140141
<lang.namesValidator language="Mathematica" implementationClass="de.halirutan.mathematica.refactoring.MathematicaNamesValidator"/>

src/de/halirutan/mathematica/codeinsight/editoractions/enter/MathematicaEnterAfterOperatorHandler.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,16 +71,15 @@ public Result preprocessEnter(@NotNull final PsiFile file,
7171
final int offset = caretOffset.get();
7272
final int lineNumber = document.getLineNumber(offset);
7373
final int lineStartOffset1 = document.getLineStartOffset(lineNumber);
74-
final int lineStartOffset = lineStartOffset1;
75-
final int prevLineStartOffset = lineNumber > 0 ? document.getLineStartOffset(lineNumber - 1) : lineStartOffset;
74+
final int prevLineStartOffset = lineNumber > 0 ? document.getLineStartOffset(lineNumber - 1) : lineStartOffset1;
7675

7776
if (project == null || offset <= 0) {
7877
return Result.Continue;
7978
}
8079

8180
final EditorHighlighter highlighter = ((EditorEx)editor).getHighlighter();
8281
final HighlighterIterator iterator = highlighter.createIterator(caretOffset.get() - 1);
83-
final IElementType type = getNonWhitespaceElementType(iterator, lineStartOffset, prevLineStartOffset);
82+
final IElementType type = getNonWhitespaceElementType(iterator, lineStartOffset1, prevLineStartOffset);
8483

8584
if (MathematicaElementTypes.INDENTABLE_OPERATORS.contains(type)) {
8685
final CodeStyleSettings currentSettings = CodeStyleSettingsManager.getInstance(project).getCurrentSettings();

src/de/halirutan/mathematica/codeinsight/editoractions/enter/MathematicaEnterInsideCommentHandler.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,6 @@ public Result preprocessEnter(@NotNull final PsiFile file,
104104
@NotNull final Ref<Integer> caretAdvance,
105105
@NotNull final DataContext dataContext,
106106
final EditorActionHandler originalHandler) {
107-
108107
final Result result = skipWithResultQ(file, editor, dataContext);
109108
if (result != null) {
110109
return result;

src/de/halirutan/mathematica/codeinsight/surround/AbstractSurrounder.java

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -52,16 +52,22 @@ public boolean isApplicable(@NotNull PsiElement[] elements) {
5252
@Nullable
5353
@Override
5454
public TextRange surroundElements(@NotNull Project project, @NotNull Editor editor, @NotNull PsiElement[] elements) throws IncorrectOperationException {
55+
int selectionStart;
56+
int selectionEnd;
5557
final SelectionModel selectionModel = editor.getSelectionModel();
56-
if (selectionModel.hasSelection()) {
57-
final Document document = editor.getDocument();
58-
if (document.isWritable()) {
59-
final int selectionStart = selectionModel.getSelectionStart();
60-
final int selectionEnd = selectionModel.getSelectionEnd();
61-
final String expr = document.getText(TextRange.create(selectionStart, selectionEnd));
62-
document.replaceString(selectionStart, selectionEnd, getOpening() + expr + getClosing());
63-
modifySelection(TextRange.create(selectionStart,selectionEnd), selectionModel);
64-
}
58+
59+
if (elements.length > 0) {
60+
selectionStart = elements[0].getTextOffset();
61+
selectionEnd = elements[elements.length - 1].getTextRange().getEndOffset();
62+
} else {
63+
return null;
64+
}
65+
66+
final Document document = editor.getDocument();
67+
if (document.isWritable()) {
68+
final String expr = document.getText(TextRange.create(selectionStart, selectionEnd));
69+
document.replaceString(selectionStart, selectionEnd, getOpening() + expr + getClosing());
70+
modifySelection(TextRange.create(selectionStart, selectionEnd), selectionModel);
6571
}
6672
return null;
6773
}

src/de/halirutan/mathematica/codeinsight/surround/MathematicaSurroundDescriptor.java

Lines changed: 14 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2014 Patrick Scheibe
2+
* Copyright (c) 2017 Patrick Scheibe
33
* Permission is hereby granted, free of charge, to any person obtaining a copy
44
* of this software and associated documentation files (the "Software"), to deal
55
* in the Software without restriction, including without limitation the rights
@@ -25,12 +25,11 @@
2525
import com.intellij.lang.surroundWith.Surrounder;
2626
import com.intellij.psi.PsiElement;
2727
import com.intellij.psi.PsiFile;
28-
import com.intellij.psi.PsiWhiteSpace;
2928
import com.intellij.psi.util.PsiTreeUtil;
30-
import de.halirutan.mathematica.parsing.psi.api.Expression;
3129
import org.jetbrains.annotations.NotNull;
3230

3331
/**
32+
* Extension point that defines available surrounders.
3433
* @author patrick (6/11/14)
3534
*/
3635
public class MathematicaSurroundDescriptor implements SurroundDescriptor {
@@ -42,11 +41,6 @@ public class MathematicaSurroundDescriptor implements SurroundDescriptor {
4241
new AnonymousFunctionSurrounder()
4342
};
4443

45-
@NotNull
46-
public PsiElement[] getElementsToSurround(PsiFile file, int startOffset, int endOffset) {
47-
return findElementsInRange(file, startOffset, endOffset);
48-
}
49-
5044
@NotNull
5145
public Surrounder[] getSurrounders() {
5246
return SURROUNDERS;
@@ -57,6 +51,12 @@ public boolean isExclusive() {
5751
return false;
5852
}
5953

54+
@NotNull
55+
@Override
56+
public PsiElement[] getElementsToSurround(PsiFile file, int startOffset, int endOffset) {
57+
return findElementsInRange(file, startOffset, endOffset);
58+
}
59+
6060
private PsiElement[] findElementsInRange(PsiFile file, int startOffset, int endOffset) {
6161

6262
if (endOffset < startOffset) {
@@ -65,30 +65,17 @@ private PsiElement[] findElementsInRange(PsiFile file, int startOffset, int endO
6565
startOffset = tmp;
6666
}
6767

68-
// adjust start/end
69-
PsiElement element1 = file.findElementAt(startOffset);
70-
PsiElement element2 = file.findElementAt(endOffset - 1);
71-
if (element1 instanceof PsiWhiteSpace) {
72-
startOffset = element1.getTextRange().getEndOffset();
73-
}
74-
if (element2 instanceof PsiWhiteSpace) {
75-
endOffset = element2.getTextRange().getStartOffset();
76-
}
7768
final PsiElement elementAtStart = file.findElementAt(startOffset);
7869
final PsiElement elementAtEnd = file.findElementAt(endOffset - 1);
7970
if (elementAtStart != null && elementAtEnd != null) {
8071

81-
if (elementAtStart == elementAtEnd && !(elementAtStart instanceof Expression)) {
82-
final PsiElement elementToSurround = elementAtStart.getParent();
83-
if (elementToSurround instanceof Expression) {
84-
return new PsiElement[]{elementToSurround};
85-
}
86-
} else {
87-
88-
final PsiElement commonContext = PsiTreeUtil.findCommonParent(elementAtStart, elementAtEnd);
89-
if (commonContext != null)
90-
return new PsiElement[]{commonContext};
72+
final PsiElement parent = PsiTreeUtil.findCommonContext(elementAtStart, elementAtEnd);
73+
if (parent != null && parent.getTextRange().equalsToRange(startOffset, endOffset)) {
74+
return new PsiElement[]{parent};
9175
}
76+
77+
return new PsiElement[]{elementAtStart, elementAtEnd};
78+
9279
}
9380
return PsiElement.EMPTY_ARRAY;
9481
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/*
2+
* Copyright (c) 2017 Patrick Scheibe
3+
* Permission is hereby granted, free of charge, to any person obtaining a copy
4+
* of this software and associated documentation files (the "Software"), to deal
5+
* in the Software without restriction, including without limitation the rights
6+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
* copies of the Software, and to permit persons to whom the Software is
8+
* furnished to do so, subject to the following conditions:
9+
*
10+
* The above copyright notice and this permission notice shall be included in
11+
* all copies or substantial portions of the Software.
12+
*
13+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
* THE SOFTWARE.
20+
*/
21+
22+
package de.halirutan.mathematica.codeinsight.surround;
23+
24+
import com.intellij.codeInsight.generation.surroundWith.SurroundWithRangeAdjuster;
25+
import com.intellij.openapi.editor.Editor;
26+
import com.intellij.openapi.fileEditor.FileEditorManager;
27+
import com.intellij.openapi.project.Project;
28+
import com.intellij.openapi.util.TextRange;
29+
import com.intellij.psi.FileViewProvider;
30+
import com.intellij.psi.PsiElement;
31+
import com.intellij.psi.PsiFile;
32+
import com.intellij.psi.PsiWhiteSpace;
33+
import com.intellij.psi.util.PsiUtilBase;
34+
import de.halirutan.mathematica.MathematicaLanguage;
35+
import de.halirutan.mathematica.parsing.psi.api.Expression;
36+
import org.jetbrains.annotations.Nullable;
37+
38+
/**
39+
* The range adjuster has two purposes: If a user called surroundWith having an explicit selection, we just remove the
40+
* whitespace before and after the selection. If the user has not selected anything it is up to us to find a good
41+
* expression near the caret that is going to be surrounded. For this, we use {@link SurroundExpressionFinder} to inspect
42+
* the syntax tree and we return the range hopefully a useful expression.
43+
*
44+
* @author patrick (04.06.17).
45+
*/
46+
public class MathematicaSurroundWithRangeAdjuster implements SurroundWithRangeAdjuster {
47+
48+
@Nullable
49+
@Override
50+
public TextRange adjustSurroundWithRange(PsiFile file, TextRange selectedRange) {
51+
return adjustSurroundWithRange(file, selectedRange, true);
52+
}
53+
54+
@Nullable
55+
@Override
56+
public TextRange adjustSurroundWithRange(PsiFile file, TextRange selectedRange, boolean hasSelection) {
57+
int startOffset = selectedRange.getStartOffset();
58+
int endOffset = selectedRange.getEndOffset();
59+
if (endOffset < startOffset) {
60+
int tmp = endOffset;
61+
endOffset = startOffset;
62+
startOffset = tmp;
63+
}
64+
65+
if (hasSelection) {
66+
final FileViewProvider viewProvider = file.getViewProvider();
67+
PsiElement element1 = viewProvider.findElementAt(startOffset, MathematicaLanguage.INSTANCE);
68+
PsiElement element2 = viewProvider.findElementAt(endOffset - 1, MathematicaLanguage.INSTANCE);
69+
if (element1 instanceof PsiWhiteSpace) {
70+
startOffset = element1.getTextRange().getEndOffset();
71+
element1 = file.findElementAt(startOffset);
72+
}
73+
if (element2 instanceof PsiWhiteSpace) {
74+
endOffset = element2.getTextRange().getStartOffset();
75+
element2 = file.findElementAt(endOffset - 1);
76+
}
77+
if (element1 != null && element2 != null) {
78+
final int startOffset1 = element1.getTextRange().getStartOffset();
79+
final int endOffset1 = element2.getTextRange().getEndOffset();
80+
if (startOffset1 < endOffset1) {
81+
return TextRange.create(startOffset1, endOffset1);
82+
}
83+
}
84+
return null;
85+
} else {
86+
final Project project = file.getProject();
87+
final Editor selectedTextEditor = FileEditorManager.getInstance(project).getSelectedTextEditor();
88+
if (selectedTextEditor == null) {
89+
return null;
90+
}
91+
92+
PsiElement element = PsiUtilBase.getElementAtCaret(selectedTextEditor);
93+
if (element == null) {
94+
return null;
95+
}
96+
97+
SurroundExpressionFinder expressionFinder = new SurroundExpressionFinder();
98+
element.accept(expressionFinder);
99+
PsiElement bestExpression = expressionFinder.getBestExpression();
100+
if (bestExpression instanceof Expression) {
101+
final TextRange textRange = bestExpression.getTextRange();
102+
selectedTextEditor.getSelectionModel().setSelection(textRange.getStartOffset(), textRange.getEndOffset());
103+
return bestExpression.getTextRange();
104+
}
105+
}
106+
return null;
107+
}
108+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/*
2+
* Copyright (c) 2017 Patrick Scheibe
3+
* Permission is hereby granted, free of charge, to any person obtaining a copy
4+
* of this software and associated documentation files (the "Software"), to deal
5+
* in the Software without restriction, including without limitation the rights
6+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
* copies of the Software, and to permit persons to whom the Software is
8+
* furnished to do so, subject to the following conditions:
9+
*
10+
* The above copyright notice and this permission notice shall be included in
11+
* all copies or substantial portions of the Software.
12+
*
13+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
* THE SOFTWARE.
20+
*/
21+
22+
package de.halirutan.mathematica.codeinsight.surround;
23+
24+
import com.intellij.psi.PsiElement;
25+
import com.intellij.psi.PsiFile;
26+
import com.intellij.psi.PsiWhiteSpace;
27+
import com.intellij.psi.impl.source.tree.LeafPsiElement;
28+
import de.halirutan.mathematica.parsing.psi.MathematicaVisitor;
29+
import de.halirutan.mathematica.parsing.psi.api.CompoundExpression;
30+
import de.halirutan.mathematica.parsing.psi.api.Number;
31+
import de.halirutan.mathematica.parsing.psi.api.StringifiedSymbol;
32+
import de.halirutan.mathematica.parsing.psi.api.Symbol;
33+
import de.halirutan.mathematica.parsing.psi.api.slots.Slot;
34+
import de.halirutan.mathematica.parsing.psi.api.slots.SlotExpression;
35+
36+
/**
37+
* When SurroundWith is pressed without having an explicit selection, we need to find the best expression the user
38+
* might want to surround. This visitor will try to achieve this by walking upwards in the syntax tree and ignoring
39+
* useless candidates. For instance when the caret is over a symbol it is often not wanted to surround this symbol.
40+
* Instead we are looking for the wider context which often is a function call, a list, an association, a rule, or any
41+
* other non-trivial expression.
42+
* <p>
43+
* Be aware that this will only be called when the user did not explicitly made a selection and really asks us to make
44+
* an educated guess!
45+
*
46+
* @author patrick (04.06.17).
47+
*/
48+
public class SurroundExpressionFinder extends MathematicaVisitor {
49+
50+
private PsiElement myBestExpression = null;
51+
52+
PsiElement getBestExpression() {
53+
return myBestExpression;
54+
}
55+
56+
private void walkUp(PsiElement element) {
57+
if (element.getContext() != null) {
58+
element.getContext().accept(this);
59+
}
60+
}
61+
62+
@Override
63+
public void visitElement(PsiElement element) {
64+
if (element instanceof LeafPsiElement) {
65+
walkUp(element);
66+
} else {
67+
myBestExpression = element;
68+
}
69+
}
70+
71+
@Override
72+
public void visitFile(PsiFile file) {
73+
}
74+
75+
@Override
76+
public void visitCompoundExpression(CompoundExpression compoundExpression) {
77+
walkUp(compoundExpression);
78+
}
79+
80+
@Override
81+
public void visitSymbol(Symbol symbol) {
82+
walkUp(symbol);
83+
}
84+
85+
@Override
86+
public void visitStringifiedSymbol(StringifiedSymbol stringifiedSymbol) {
87+
walkUp(stringifiedSymbol);
88+
}
89+
90+
@Override
91+
public void visitSlot(Slot slot) {
92+
walkUp(slot);
93+
}
94+
95+
@Override
96+
public void visitSlotExpression(SlotExpression slotExpr) {
97+
walkUp(slotExpr);
98+
}
99+
100+
@Override
101+
public void visitWhiteSpace(PsiWhiteSpace space) {
102+
walkUp(space);
103+
}
104+
105+
@Override
106+
public void visitNumber(Number number) {
107+
walkUp(number);
108+
}
109+
}

0 commit comments

Comments
 (0)