Skip to content

Commit 21eee47

Browse files
committed
Fix MIT license and add CHANGELOG
Implement QuickDoc for usage messages of user functions
1 parent 725dea7 commit 21eee47

10 files changed

Lines changed: 236 additions & 68 deletions

File tree

CHANGELOG.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#Change Log
2+
3+
## Todo
4+
5+
- Show usage for own functions. "Quick Documentation" on a package symbol will show its usage message if available
6+
- Better rendering for "Go to Declaration" targets
7+
- Inspection for using the same variable in the e.g. Module definition list several times
8+
- Quick fix for adding usages and other messages
9+
- Keeping a leading * when pressing enter inside comments
10+
- Rework of creating project templates and modules
11+
- Update to Mathematica version 11.2
12+
- Quick fix for pushing a variable inside the Module definition list
13+
- Completion for file names inside strings
14+
15+
## Version 3.0
16+
17+
- Performance improvement through caching
18+
- Support for Libraries. Libraries are basically a package folder with Mathematica source code. This code is indexed and
19+
can now be used for completion and navigation. The highlighter shows which functions come from a different file
20+
- Support for project-wide navigation and resolving of symbols. Now you can refactor, navigate and complete functions
21+
that are located in a different package file
22+
- Smart completion inside comments to insert symbols from file
23+
- Implementation of better local variable support. Especially, "With" supports now several declaration lists
24+
- Fixing "Go to related symbol". Finds all usages of a symbol and gives file, line and a snip of the code there. You can
25+
directly navigate to any of these places
26+
- Implemented "Go to Declaration" so that the user can navigate to all targets where a function is given a definition (
27+
I know that go to _declaration_ is misleading for Mathematica, but it is the easier short-cut and widely used.)
28+
- Heavy reimplementation of the core algorithms for resolving references.

config/copyright/MIT.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<component name="CopyrightManager">
22
<copyright>
3-
<option name="notice" value="Copyright (c) &amp;#36;today.year Patrick Scheibe&#10;&#10; Permission is hereby granted, free of charge, to any person obtaining a copy&#10; of this software and associated documentation files (the &quot;Software&quot;), to deal&#10; in the Software without restriction, including without limitation the rights&#10; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&#10; copies of the Software, and to permit persons to whom the Software is&#10; furnished to do so, subject to the following conditions:&#10; &#10; The above copyright notice and this permission notice shall be included in&#10; all copies or substantial portions of the Software.&#10; &#10; THE SOFTWARE IS PROVIDED &quot;AS IS&quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&#10; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&#10; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&#10; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&#10; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&#10; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN&#10; THE SOFTWARE.&#10; " />
3+
<option name="notice" value="Copyright (c) &amp;#36;today.year. Patrick Scheibe&#10;&#10;Permission is hereby granted, free of charge, to any person&#10;obtaining a copy of this software and associated documentation&#10;files (the “Software”), to deal in the Software without&#10;restriction, including without limitation the rights to use,&#10;copy, modify, merge, publish, distribute, sublicense, and/or sell&#10;copies of the Software, and to permit persons to whom the&#10;Software is furnished to do so, subject to the following&#10;conditions:&#10;&#10;The above copyright notice and this permission notice shall be&#10;included in all copies or substantial portions of the Software.&#10;&#10;THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,&#10;EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES&#10;OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND&#10;NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT&#10;HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,&#10;WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING&#10;FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR&#10;OTHER DEALINGS IN THE SOFTWARE." />
44
</copyright>
55
</component>

resources/de/halirutan/mathematica/MathematicaBundle.properties

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323

2424
language.name=Mathematica
2525
language.description=The Mathematica/Wolfram language
26+
language.level.11.3=Mathematica Version 11.3
27+
language.level.11.2=Mathematica Version 11.2
28+
language.level.11.1=Mathematica Version 11.1
2629
language.level.11=Mathematica Version 11.0
2730
language.level.10.4=Mathematica Version 10.4
2831
language.level.10.3=Mathematica Version 10.3
@@ -54,3 +57,6 @@ structureview.sorter.by.appearance.text=Sort by Appearance
5457
structureview.sorter.by.appearance.description=Sorts the entries by appearance in the source file.
5558
library.new=Mathematica Library
5659
library.description=A Mathematica package directory that acts as library
60+
doc.navi.invalid=Cannot resolve symbol {0}
61+
doc.navi.navto=Navigate to definition of {0}
62+
doc.navi.builtin=Built-in symbol <b>{0}</b>

src/de/halirutan/mathematica/documentation/MathematicaDocumentationProvider.java

Lines changed: 75 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -32,20 +32,26 @@
3232
import com.intellij.psi.PsiFile;
3333
import com.intellij.psi.PsiManager;
3434
import com.intellij.psi.util.PsiTreeUtil;
35+
import de.halirutan.mathematica.MathematicaBundle;
3536
import de.halirutan.mathematica.lang.psi.LocalizationConstruct;
3637
import de.halirutan.mathematica.lang.psi.api.OperatorNameProvider;
3738
import de.halirutan.mathematica.lang.psi.api.Symbol;
3839
import de.halirutan.mathematica.lang.psi.impl.LightBuiltInSymbol;
3940
import de.halirutan.mathematica.lang.psi.impl.LightFileSymbol;
4041
import de.halirutan.mathematica.lang.psi.util.MathematicaPsiElementFactory;
42+
import de.halirutan.mathematica.lang.psi.util.UsageMessagesKt;
43+
import kotlin.Pair;
4144
import org.jetbrains.annotations.NotNull;
4245
import org.jetbrains.annotations.Nullable;
4346

4447
import java.io.InputStream;
48+
import java.util.List;
4549
import java.util.Scanner;
4650
import java.util.regex.Pattern;
4751

4852
/**
53+
* Provides documentation aka rendered usage messages for built-in functions, operators and user-defined functions
54+
*
4955
* @author patrick (4/4/13)
5056
*/
5157
public class MathematicaDocumentationProvider extends AbstractDocumentationProvider {
@@ -55,10 +61,13 @@ public class MathematicaDocumentationProvider extends AbstractDocumentationProvi
5561
private static final Pattern SLOT_SEQUENCE_PATTERN = Pattern.compile("##[0-9]*");
5662

5763
/**
58-
* Generates the documentation (if available) for element. This does two things, first it looks whether the element is
59-
* a {@link Symbol}. If this is true it tries to load the usage message. If element is not a Symbol, it is possibly an
60-
* operator. Then it tries to guess the usage message of the operator by converting the class name to a hopefully
61-
* valid operator name.
64+
* Generates the documentation (if available) for element. This does three things:
65+
* <p>
66+
* <ul>
67+
* <li>it provides documentation if the symbol near the cursor is a built in function</li>
68+
* <li>it provides documentation when it is called over operators like ++</li>
69+
* <li>it checks if user functions have a usage message and presents this</li>
70+
* </ul>
6271
*
6372
* @param element Element which was possibly altered by {@link #getCustomDocumentationElement(Editor, PsiFile, PsiElement)} or by
6473
* {@link #getDocumentationElementForLookupItem(PsiManager, Object, PsiElement)} if the lookup was active
@@ -70,36 +79,68 @@ public class MathematicaDocumentationProvider extends AbstractDocumentationProvi
7079
@Override
7180
public String generateDoc(PsiElement element, @Nullable PsiElement originalElement) {
7281

73-
Symbol docElement;
74-
if (element instanceof Symbol) {
75-
docElement = (Symbol) element;
76-
} else if (originalElement instanceof Symbol) {
77-
docElement = (Symbol) originalElement;
78-
} else {
82+
if (!(element instanceof Symbol || element instanceof OperatorNameProvider)) {
7983
return null;
8084
}
8185

86+
String pathToHTMLDoc;
8287

83-
String context = docElement.getMathematicaContext();
84-
context = context.equals("") ? "System`" : context;
85-
String name = docElement.getSymbolName();
86-
if (ALL_SLOT_PATTERN.matcher(name).matches()) {
87-
if (SLOT_PATTERN.matcher(name).matches()) name = "Slot";
88-
else if (SLOT_SEQUENCE_PATTERN.matcher(name).matches()) name = "SlotSequence";
88+
if (element instanceof Symbol) {
89+
Symbol docElement = (Symbol) element;
90+
String context = docElement.getMathematicaContext();
91+
context = context.equals("") ? "System`" : context;
92+
String name = docElement.getSymbolName();
93+
if (ALL_SLOT_PATTERN.matcher(name).matches()) {
94+
if (SLOT_PATTERN.matcher(name).matches()) name = "Slot";
95+
else if (SLOT_SEQUENCE_PATTERN.matcher(name).matches()) name = "SlotSequence";
96+
}
97+
pathToHTMLDoc = "usages/" + context.replace('`', '/') + name + ".html";
98+
} else {
99+
pathToHTMLDoc = "usages/System/" + ((OperatorNameProvider) element).getOperatorName() + ".html";
89100
}
90-
String path = "usages/" + context.replace('`', '/') + name + ".html";
91101

92-
if (docElement instanceof OperatorNameProvider) {
93-
path = "usages/System/" + ((OperatorNameProvider) docElement).getOperatorName() + ".html";
102+
InputStream docFile = MathematicaDocumentationProvider.class.getResourceAsStream(pathToHTMLDoc);
103+
if (docFile != null) {
104+
final String usage = new Scanner(docFile, "UTF-8").useDelimiter("\\A").next();
105+
if (!usage.isEmpty()) {
106+
return usage;
107+
}
94108
}
95109

96-
InputStream docFile = MathematicaDocumentationProvider.class.getResourceAsStream(path);
97-
if (docFile != null) {
98-
return new Scanner(docFile, "UTF-8").useDelimiter("\\A").next();
110+
// Inject the usage message of functions that are declared inside the package
111+
if (element instanceof Symbol) {
112+
return renderCustomUsageMessage((Symbol) element);
99113
}
114+
100115
return null;
101116
}
102117

118+
/**
119+
* Provides a html form of the usage message for custom user functions.
120+
* @param symbol a file symbol for which the usage message should be found and rendered
121+
* @return the first found usage message
122+
*/
123+
private String renderCustomUsageMessage(@NotNull Symbol symbol) {
124+
final Pair<Symbol, List<String>> usages = UsageMessagesKt.extractUsageMessageString(symbol);
125+
if (usages.component2().isEmpty()) {
126+
return "";
127+
}
128+
final String fileName = usages.component1().getContainingFile().getName();
129+
final String symbolName = symbol.getSymbolName();
130+
StringBuilder result = new StringBuilder("<h3>");
131+
result.append(symbolName);
132+
result.append(" (");
133+
result.append(fileName);
134+
result.append(")</h3><ul>");
135+
for (String usg : usages.component2()) {
136+
result.append("<li>");
137+
result.append(usg.replaceAll("("+symbolName+"(\\[.*])?)", "<b>$1</b>"));
138+
result.append("</li>");
139+
}
140+
result.append("</ul>");
141+
return result.toString();
142+
}
143+
103144
/**
104145
* Calculates the correct element for which the user wants documentation.
105146
*
@@ -137,9 +178,7 @@ public PsiElement getCustomDocumentationElement(@NotNull Editor editor, @NotNull
137178
// Determine if the contextElement is the operator sign of an operation.
138179
// See the doc to OperatorNameProviderImpl.
139180
if (docElement instanceof OperatorNameProvider) {
140-
// if (((OperatorNameProvider) docElement).isOperatorSign(contextElement)) {
141181
return docElement;
142-
// }
143182
}
144183
return null;
145184
}
@@ -161,7 +200,8 @@ public PsiElement getCustomDocumentationElement(@NotNull Editor editor, @NotNull
161200
@Override
162201
public PsiElement getDocumentationElementForLookupItem(PsiManager psiManager, Object object, PsiElement element) {
163202
if (element != null) {
164-
final LookupEx activeLookup = LookupManager.getActiveLookup(FileEditorManager.getInstance(element.getProject()).getSelectedTextEditor());
203+
final LookupEx activeLookup =
204+
LookupManager.getActiveLookup(FileEditorManager.getInstance(element.getProject()).getSelectedTextEditor());
165205
if (activeLookup != null) {
166206
MathematicaPsiElementFactory elementFactory = new MathematicaPsiElementFactory(psiManager.getProject());
167207
try {
@@ -174,21 +214,27 @@ public PsiElement getDocumentationElementForLookupItem(PsiManager psiManager, Ob
174214
return null;
175215
}
176216

217+
/**
218+
* Provides a quick info when the user hovers code with CTRL pressed
219+
* @param element element to show quick navigation. This is the resolved element which means we can check which kind of light symbol it is
220+
* @param originalElement the original symbol in file
221+
* @return quick info what happens when the user performs a mouse click
222+
*/
177223
@Nullable
178224
@Override
179225
public String getQuickNavigateInfo(PsiElement element, PsiElement originalElement) {
180226
if (element instanceof LightBuiltInSymbol) {
181-
return "Built-in symbol " + ((LightBuiltInSymbol) element).getName();
227+
return MathematicaBundle.message("doc.navi.builtin", ((LightBuiltInSymbol) element).getName());
182228
}
183229
if (element instanceof LightFileSymbol) {
184230
if (originalElement instanceof Symbol &&
185231
((Symbol) originalElement).getLocalizationConstruct() == LocalizationConstruct.MScope.NULL_SCOPE) {
186-
return "Cannot resolve symbol " + ((LightFileSymbol) element).getName();
232+
return MathematicaBundle.message("doc.navi.invalid", ((LightFileSymbol) element).getName());
187233
}
188-
return "Navigate to definition of " + ((LightFileSymbol) element).getName();
234+
return MathematicaBundle.message("doc.navi.navto", ((LightFileSymbol) element).getName());
189235
}
190236
if (element instanceof Symbol) {
191-
return "Navigate to definition of " + ((Symbol) element).getFullSymbolName();
237+
return MathematicaBundle.message("doc.navi.navto", ((Symbol) element).getFullSymbolName());
192238
}
193239
return super.getQuickNavigateInfo(element, originalElement);
194240
}

src/de/halirutan/mathematica/lang/psi/api/MessageName.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,6 @@ public interface MessageName extends Expression {
3636

3737
@Nullable
3838
public StringifiedSymbol getLang();
39+
40+
public boolean isUsageMessage();
3941
}

src/de/halirutan/mathematica/lang/psi/impl/MessageNameImpl.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import de.halirutan.mathematica.lang.psi.api.Expression;
2929
import de.halirutan.mathematica.lang.psi.api.MessageName;
3030
import de.halirutan.mathematica.lang.psi.api.StringifiedSymbol;
31+
import de.halirutan.mathematica.lang.psi.api.string.MString;
3132
import org.jetbrains.annotations.NotNull;
3233
import org.jetbrains.annotations.Nullable;
3334

@@ -83,4 +84,10 @@ public StringifiedSymbol getLang() {
8384
}
8485
return null;
8586
}
87+
88+
@Override
89+
public boolean isUsageMessage() {
90+
final StringifiedSymbol tag = getTag();
91+
return tag != null && "usage".equals(tag.getText());
92+
}
8693
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package de.halirutan.mathematica.lang.psi.util
2+
3+
import com.intellij.openapi.progress.ProgressManager
4+
import com.intellij.openapi.project.DumbService
5+
import com.intellij.psi.search.searches.ReferencesSearch
6+
import com.intellij.psi.util.PsiTreeUtil
7+
import de.halirutan.mathematica.lang.psi.api.MessageName
8+
import de.halirutan.mathematica.lang.psi.api.Symbol
9+
import de.halirutan.mathematica.lang.psi.api.assignment.Set
10+
import de.halirutan.mathematica.lang.psi.api.string.MString
11+
12+
/**
13+
*
14+
* @author patrick (02.12.17).
15+
*/
16+
17+
fun extractUsageMessageString(symbol: Symbol): Pair<Symbol, List<String>> {
18+
val resolve = symbol.resolve() ?: return Pair(symbol, emptyList())
19+
20+
if (resolve is Symbol) {
21+
return Pair(resolve, doUsageMessageExtract(resolve))
22+
}
23+
24+
var result: Pair<Symbol, List<String>> = Pair(symbol, emptyList())
25+
26+
DumbService.getInstance(symbol.project).runReadActionInSmartMode {
27+
val elements = ReferencesSearch.search(resolve)
28+
for (elm in elements) {
29+
ProgressManager.checkCanceled()
30+
if (elm != null && elm.element is Symbol) {
31+
val sym = elm.element as Symbol
32+
val r = doUsageMessageExtract(sym)
33+
if (r.isNotEmpty()) {
34+
result = Pair(sym, r)
35+
break
36+
}
37+
}
38+
}
39+
}
40+
return result
41+
}
42+
43+
private fun doUsageMessageExtract(symbol: Symbol): List<String> {
44+
if (symbol.parent is MessageName) {
45+
val messageName = symbol.parent as MessageName
46+
if (messageName.isUsageMessage) {
47+
val set = messageName.parent
48+
if (set is Set) {
49+
var message = ""
50+
when {
51+
set.lastChild is MString -> message = set.lastChild.text.removeSurrounding("\"")
52+
else -> {
53+
val strings = PsiTreeUtil.findChildrenOfType(set.lastChild, MString::class.java)
54+
strings.forEach { result -> message += result.text.removeSurrounding("\"") }
55+
}
56+
}
57+
return message.replace("\\[Ellipsis]", "...").split("\\n")
58+
}
59+
}
60+
}
61+
return emptyList()
62+
}

src/de/halirutan/mathematica/lang/resolve/MathematicaGlobalResolveCache.java

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ public boolean containsSymbol(@NotNull Symbol symbol) {
6767
return containsBuildInSymbol(symbol) || containsFileSymbol(symbol) || containsExternalSymbol(symbol);
6868
}
6969

70-
private boolean containsFileSymbol(@NotNull Symbol symbol) {
70+
public boolean containsFileSymbol(@NotNull Symbol symbol) {
7171
final LightFileSymbol lightFileSymbol = new LightFileSymbol(symbol);
7272
return myCachedFileSymbols.containsKey(lightFileSymbol);
7373
}
@@ -131,9 +131,19 @@ public ResolveResult getValue(Symbol symbol) {
131131
return myCachedExternalSymbols.get(new LightExternalSymbol(symbol));
132132
}
133133

134+
public List<SymbolResolveResult> getCachedFileSymbolResolves(@NotNull PsiFile containingFile) {
135+
return myCachedFileSymbols
136+
.values()
137+
.parallelStream()
138+
.filter(resolve -> containingFile.equals(resolve.getScopingElement()) &&
139+
resolve.getElement() instanceof LightFileSymbol)
140+
.collect(Collectors.toList());
141+
}
142+
134143
public List<String> getCachedFileSymbolNames(@NotNull PsiFile file) {
135-
return myCachedFileSymbols.values().parallelStream().filter(
136-
resolve -> file.equals(resolve.getScopingElement()) && resolve.getElement() instanceof LightFileSymbol).map(
137-
resolve -> ((LightFileSymbol) resolve.getElement()).getName()).collect(Collectors.toList());
144+
return getCachedFileSymbolResolves(file)
145+
.stream()
146+
.filter(resolve -> resolve.getElement() != null)
147+
.map(resolve -> ((LightFileSymbol) resolve.getElement()).getName()).collect(Collectors.toList());
138148
}
139149
}

0 commit comments

Comments
 (0)