1 package org.simantics.scl.ui.editor.completion;
3 import java.io.StringReader;
4 import java.util.ArrayList;
5 import java.util.Collections;
6 import java.util.Comparator;
9 import org.eclipse.jface.text.IDocument;
10 import org.eclipse.jface.text.contentassist.ICompletionProposal;
11 import org.simantics.scl.compiler.common.names.Name;
12 import org.simantics.scl.compiler.compilation.EnvironmentOfModule;
13 import org.simantics.scl.compiler.elaboration.modules.SCLValue;
14 import org.simantics.scl.compiler.environment.AmbiguousNameException;
15 import org.simantics.scl.compiler.environment.Environment;
16 import org.simantics.scl.compiler.environment.Environments;
17 import org.simantics.scl.compiler.errors.Failable;
18 import org.simantics.scl.compiler.internal.parsing.exceptions.SCLSyntaxErrorException;
19 import org.simantics.scl.compiler.internal.parsing.parser.SCLParserImpl;
20 import org.simantics.scl.compiler.module.ImportDeclaration;
21 import org.simantics.scl.compiler.module.Module;
22 import org.simantics.scl.compiler.module.repository.ImportFailureException;
23 import org.simantics.scl.compiler.types.TCon;
24 import org.simantics.scl.osgi.SCLOsgi;
26 public class SCLTextEditorEnvironment {
28 private String moduleName;
29 private SCLCompletionProposal[] proposalCache = new SCLCompletionProposal[0];
30 private List<SCLCompletionProposal> moduleProposalCache = new ArrayList<>(0);
31 private Environment env;
33 private List<ImportDeclaration> cachedImports = new ArrayList<>();
34 private boolean cacheUpdated = false;
36 public SCLTextEditorEnvironment(String moduleName) {
37 this.moduleName = moduleName;
40 public void updateModuleName(String moduleName) {
41 this.moduleName = moduleName;
44 public void updateEnvironment(IDocument document) {
45 String contents = document.get();
46 String[] lines = contents.split("\\R+");
47 List<ImportDeclaration> imports = new ArrayList<>();
48 imports.add(new ImportDeclaration("StandardLibrary", ""));
49 for (String line : lines) {
51 if (line.startsWith("import") || line.startsWith("include")) {
52 SCLParserImpl parser = new SCLParserImpl(new StringReader(line));
54 ImportDeclaration importDecl = (ImportDeclaration)parser.parseImport();
55 imports.add(importDecl);
56 } catch (SCLSyntaxErrorException e) {
57 // Import cannot be handled, ignore
62 imports = processRelativeImports(imports);
63 if (!imports.equals(cachedImports)) {
64 cachedImports = imports;
66 env = SCLOsgi.MODULE_REPOSITORY.createEnvironment(cachedImports.toArray(new ImportDeclaration[cachedImports.size()]), null);
67 Failable<Module> module = SCLOsgi.MODULE_REPOSITORY.getModule(moduleName);
68 if(module.didSucceed())
69 env = new EnvironmentOfModule(env, module.getResult());
70 } catch (ImportFailureException e) {
71 //e.printStackTrace();
76 public ICompletionProposal[] getCompletionProposals(String prefix, int offset) {
77 int p = prefix.lastIndexOf('.');
78 String lastPart = p==-1 ? prefix : prefix.substring(p+1);
80 List<SCLCompletionProposal> proposals = new ArrayList<>();
81 for(SCLValue value : Environments.findValuesForPrefix(env, prefix)) {
82 Name name = value.getName();
83 if((name.module.equals(moduleName) || !value.isPrivate()) && !(name.name.contains("$") && Character.isLetter(name.name.charAt(0))))
84 proposals.add(new SCLCompletionProposal(value, offset - lastPart.length(), lastPart));
86 for(TCon type : Environments.findTypesForPrefix(env, prefix)) {
87 proposals.add(new SCLCompletionProposal(type.name, type.module, SCLCompletionType.TYPE, offset - lastPart.length(), lastPart));
90 if(!prefix.contains(".")) {
91 for (ImportDeclaration decl : cachedImports) {
92 if (decl.localName != null && !decl.localName.isEmpty() && decl.localName.toLowerCase().startsWith(prefix.toLowerCase())) {
93 proposals.add(new SCLCompletionProposal(decl.localName, decl.moduleName, SCLCompletionType.CONST, offset - prefix.length(), prefix));
97 Collections.sort(proposals, COMPARATOR);
98 moduleProposalCache = proposals;
99 proposalCache = moduleProposalCache.toArray(new SCLCompletionProposal[moduleProposalCache.size()]);
100 return proposalCache;
103 private ArrayList<ImportDeclaration> processRelativeImports(List<ImportDeclaration> relativeImports) {
104 ArrayList<ImportDeclaration> absoluteImports = new ArrayList<ImportDeclaration>(relativeImports.size());
105 for(ImportDeclaration relativeImport : relativeImports) {
106 if(relativeImport.moduleName.startsWith(".")) {
107 String absoluteModuleName = convertRelativeModulePath(relativeImport.moduleName);
108 if(absoluteModuleName != null)
109 absoluteImports.add(new ImportDeclaration(
110 absoluteModuleName, relativeImport.localName,
111 relativeImport.reexport, relativeImport.spec));
114 absoluteImports.add(relativeImport);
116 return absoluteImports;
119 private String convertRelativeModulePath(String relativeModuleName) {
120 String originalRelativeModuleName = relativeModuleName;
121 int p = moduleName.lastIndexOf('/');
122 String parentPackage = p < 0 ? "" : moduleName.substring(0, p);
123 while(relativeModuleName.startsWith(".")) {
124 if(relativeModuleName.startsWith("./")) {
125 relativeModuleName = relativeModuleName.substring(2);
127 else if(relativeModuleName.startsWith("../")) {
128 relativeModuleName = relativeModuleName.substring(3);
129 if(parentPackage.isEmpty()) {
130 System.err.println("Couldn't resolve the relative module name " + originalRelativeModuleName + " when the current module name is " + moduleName + ".");
133 p = parentPackage.lastIndexOf('/');
134 parentPackage = p < 0 ? "" : parentPackage.substring(0, p);
137 System.err.println("Couldn't resolve the relative module name " + originalRelativeModuleName + ". It has an invalid syntax.");
141 return parentPackage + "/" + relativeModuleName;
144 private static final Comparator<SCLCompletionProposal> COMPARATOR = new Comparator<SCLCompletionProposal>() {
147 public int compare(SCLCompletionProposal prop1, SCLCompletionProposal prop2) {
148 if (prop1.isPrivate() && !prop2.isPrivate())
150 else if (!prop1.isPrivate() && prop2.isPrivate())
152 return prop1.getName().compareTo(prop2.getName());
156 public SCLValue getValue(String text) {
158 return Environments.getValue(env, text);
159 } catch (AmbiguousNameException e) {
160 // TODO could also return one of the conflicting alternatives
165 public String getHoverInfo(String text) {
166 SCLValue value = getValue(text);
168 return value.getDocumentation();