1 package org.simantics.scl.ui.editor.completion;
\r
3 import java.io.StringReader;
\r
4 import java.util.ArrayList;
\r
5 import java.util.Arrays;
\r
6 import java.util.Collections;
\r
7 import java.util.Comparator;
\r
8 import java.util.List;
\r
10 import org.eclipse.jface.text.IDocument;
\r
11 import org.eclipse.jface.text.contentassist.ICompletionProposal;
\r
12 import org.simantics.scl.compiler.common.names.Name;
\r
13 import org.simantics.scl.compiler.compilation.EnvironmentOfModule;
\r
14 import org.simantics.scl.compiler.elaboration.modules.SCLValue;
\r
15 import org.simantics.scl.compiler.environment.AmbiguousNameException;
\r
16 import org.simantics.scl.compiler.environment.Environment;
\r
17 import org.simantics.scl.compiler.environment.Environments;
\r
18 import org.simantics.scl.compiler.errors.Failable;
\r
19 import org.simantics.scl.compiler.internal.parsing.exceptions.SCLSyntaxErrorException;
\r
20 import org.simantics.scl.compiler.internal.parsing.parser.SCLParserImpl;
\r
21 import org.simantics.scl.compiler.module.ImportDeclaration;
\r
22 import org.simantics.scl.compiler.module.Module;
\r
23 import org.simantics.scl.compiler.module.repository.ImportFailureException;
\r
24 import org.simantics.scl.compiler.source.TextualModuleSource;
\r
25 import org.simantics.scl.compiler.types.TCon;
\r
26 import org.simantics.scl.osgi.SCLOsgi;
\r
28 public class SCLTextEditorEnvironment {
\r
30 private String moduleName;
\r
31 private SCLCompletionProposal[] proposalCache = new SCLCompletionProposal[0];
\r
32 private List<SCLCompletionProposal> moduleProposalCache = new ArrayList<>(0);
\r
33 private Environment env;
\r
35 private List<ImportDeclaration> cachedImports = new ArrayList<>();
\r
36 private boolean cacheUpdated = false;
\r
38 public SCLTextEditorEnvironment(String moduleName) {
\r
39 this.moduleName = moduleName;
\r
42 public void updateModuleName(String moduleName) {
\r
43 this.moduleName = moduleName;
\r
46 public void updateEnvironment(IDocument document) {
\r
47 String contents = document.get();
\r
48 String[] lines = contents.split("\\R+");
\r
49 List<ImportDeclaration> imports = new ArrayList<>();
\r
50 imports.addAll(Arrays.asList(TextualModuleSource.DEFAULT_IMPORTS));
\r
51 for (String line : lines) {
\r
53 if (line.startsWith("import") || line.startsWith("include")) {
\r
54 SCLParserImpl parser = new SCLParserImpl(new StringReader(line));
\r
56 ImportDeclaration importDecl = (ImportDeclaration)parser.parseImport();
\r
57 imports.add(importDecl);
\r
58 } catch (SCLSyntaxErrorException e) {
\r
59 // Import cannot be handled, ignore
\r
64 imports = processRelativeImports(imports);
\r
65 if (!imports.equals(cachedImports)) {
\r
66 cachedImports = imports;
\r
68 env = SCLOsgi.MODULE_REPOSITORY.createEnvironment(cachedImports.toArray(new ImportDeclaration[cachedImports.size()]), null);
\r
69 Failable<Module> module = SCLOsgi.MODULE_REPOSITORY.getModule(moduleName);
\r
70 if(module.didSucceed())
\r
71 env = new EnvironmentOfModule(env, module.getResult());
\r
72 } catch (ImportFailureException e) {
\r
73 //e.printStackTrace();
\r
78 public ICompletionProposal[] getCompletionProposals(String prefix, int offset) {
\r
79 int p = prefix.lastIndexOf('.');
\r
80 String lastPart = p==-1 ? prefix : prefix.substring(p+1);
\r
82 List<SCLCompletionProposal> proposals = new ArrayList<>();
\r
83 for(SCLValue value : Environments.findValuesForPrefix(env, prefix)) {
\r
84 Name name = value.getName();
\r
85 if((name.module.equals(moduleName) || !value.isPrivate()) && !(name.name.contains("$") && Character.isLetter(name.name.charAt(0))))
\r
86 proposals.add(new SCLCompletionProposal(value, offset - lastPart.length(), lastPart));
\r
88 for(TCon type : Environments.findTypesForPrefix(env, prefix)) {
\r
89 proposals.add(new SCLCompletionProposal(type.name, type.module, SCLCompletionType.TYPE, offset - lastPart.length(), lastPart));
\r
92 if(!prefix.contains(".")) {
\r
93 for (ImportDeclaration decl : cachedImports) {
\r
94 if (decl.localName != null && !decl.localName.isEmpty() && decl.localName.toLowerCase().startsWith(prefix.toLowerCase())) {
\r
95 proposals.add(new SCLCompletionProposal(decl.localName, decl.moduleName, SCLCompletionType.CONST, offset - prefix.length(), prefix));
\r
99 Collections.sort(proposals, COMPARATOR);
\r
100 moduleProposalCache = proposals;
\r
101 proposalCache = moduleProposalCache.toArray(new SCLCompletionProposal[moduleProposalCache.size()]);
\r
102 return proposalCache;
\r
105 private ArrayList<ImportDeclaration> processRelativeImports(List<ImportDeclaration> relativeImports) {
\r
106 ArrayList<ImportDeclaration> absoluteImports = new ArrayList<ImportDeclaration>(relativeImports.size());
\r
107 for(ImportDeclaration relativeImport : relativeImports) {
\r
108 if(relativeImport.moduleName.startsWith(".")) {
\r
109 String absoluteModuleName = convertRelativeModulePath(relativeImport.moduleName);
\r
110 if(absoluteModuleName != null)
\r
111 absoluteImports.add(new ImportDeclaration(
\r
112 absoluteModuleName, relativeImport.localName,
\r
113 relativeImport.reexport, relativeImport.spec));
\r
116 absoluteImports.add(relativeImport);
\r
118 return absoluteImports;
\r
121 private String convertRelativeModulePath(String relativeModuleName) {
\r
122 String originalRelativeModuleName = relativeModuleName;
\r
123 int p = moduleName.lastIndexOf('/');
\r
124 String parentPackage = p < 0 ? "" : moduleName.substring(0, p);
\r
125 while(relativeModuleName.startsWith(".")) {
\r
126 if(relativeModuleName.startsWith("./")) {
\r
127 relativeModuleName = relativeModuleName.substring(2);
\r
129 else if(relativeModuleName.startsWith("../")) {
\r
130 relativeModuleName = relativeModuleName.substring(3);
\r
131 if(parentPackage.isEmpty()) {
\r
132 System.err.println("Couldn't resolve the relative module name " + originalRelativeModuleName + " when the current module name is " + moduleName + ".");
\r
135 p = parentPackage.lastIndexOf('/');
\r
136 parentPackage = p < 0 ? "" : parentPackage.substring(0, p);
\r
139 System.err.println("Couldn't resolve the relative module name " + originalRelativeModuleName + ". It has an invalid syntax.");
\r
143 return parentPackage + "/" + relativeModuleName;
\r
146 private static final Comparator<SCLCompletionProposal> COMPARATOR = new Comparator<SCLCompletionProposal>() {
\r
149 public int compare(SCLCompletionProposal prop1, SCLCompletionProposal prop2) {
\r
150 if (prop1.isPrivate() && !prop2.isPrivate())
\r
152 else if (!prop1.isPrivate() && prop2.isPrivate())
\r
154 return prop1.getName().compareTo(prop2.getName());
\r
158 public SCLValue getValue(String text) {
\r
160 return Environments.getValue(env, text);
\r
161 } catch (AmbiguousNameException e) {
\r
162 // TODO could also return one of the conflicting alternatives
\r
167 public String getHoverInfo(String text) {
\r
168 SCLValue value = getValue(text);
\r
170 return value.getDocumentation();
\r