package org.simantics.scl.ui.editor.completion; import java.io.StringReader; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.contentassist.ICompletionProposal; import org.simantics.scl.compiler.common.names.Name; import org.simantics.scl.compiler.compilation.EnvironmentOfModule; import org.simantics.scl.compiler.elaboration.modules.SCLValue; import org.simantics.scl.compiler.environment.AmbiguousNameException; import org.simantics.scl.compiler.environment.Environment; import org.simantics.scl.compiler.environment.Environments; import org.simantics.scl.compiler.errors.Failable; import org.simantics.scl.compiler.internal.parsing.exceptions.SCLSyntaxErrorException; import org.simantics.scl.compiler.internal.parsing.parser.SCLParserImpl; import org.simantics.scl.compiler.module.ImportDeclaration; import org.simantics.scl.compiler.module.Module; import org.simantics.scl.compiler.module.repository.ImportFailureException; import org.simantics.scl.compiler.types.TCon; import org.simantics.scl.osgi.SCLOsgi; public class SCLTextEditorEnvironment { private String moduleName; private SCLCompletionProposal[] proposalCache = new SCLCompletionProposal[0]; private List moduleProposalCache = new ArrayList<>(0); private Environment env; private List cachedImports = new ArrayList<>(); private boolean cacheUpdated = false; public SCLTextEditorEnvironment(String moduleName) { this.moduleName = moduleName; } public void updateModuleName(String moduleName) { this.moduleName = moduleName; } public void updateEnvironment(IDocument document) { String contents = document.get(); String[] lines = contents.split("\\R+"); List imports = new ArrayList<>(); imports.add(new ImportDeclaration("StandardLibrary", "")); for (String line : lines) { line = line.trim(); if (line.startsWith("import") || line.startsWith("include")) { SCLParserImpl parser = new SCLParserImpl(new StringReader(line)); try { ImportDeclaration importDecl = (ImportDeclaration)parser.parseImport(); imports.add(importDecl); } catch (SCLSyntaxErrorException e) { // Import cannot be handled, ignore } } } imports = processRelativeImports(imports); if (!imports.equals(cachedImports)) { cachedImports = imports; try { env = SCLOsgi.MODULE_REPOSITORY.createEnvironment(cachedImports.toArray(new ImportDeclaration[cachedImports.size()]), null); Failable module = SCLOsgi.MODULE_REPOSITORY.getModule(moduleName); if(module.didSucceed()) env = new EnvironmentOfModule(env, module.getResult()); } catch (ImportFailureException e) { //e.printStackTrace(); } } } public ICompletionProposal[] getCompletionProposals(String prefix, int offset) { int p = prefix.lastIndexOf('.'); String lastPart = p==-1 ? prefix : prefix.substring(p+1); List proposals = new ArrayList<>(); for(SCLValue value : Environments.findValuesForPrefix(env, prefix)) { Name name = value.getName(); if((name.module.equals(moduleName) || !value.isPrivate()) && !(name.name.contains("$") && Character.isLetter(name.name.charAt(0)))) proposals.add(new SCLCompletionProposal(value, offset - lastPart.length(), lastPart)); } for(TCon type : Environments.findTypesForPrefix(env, prefix)) { proposals.add(new SCLCompletionProposal(type.name, type.module, SCLCompletionType.TYPE, offset - lastPart.length(), lastPart)); } if(!prefix.contains(".")) { for (ImportDeclaration decl : cachedImports) { if (decl.localName != null && !decl.localName.isEmpty() && decl.localName.toLowerCase().startsWith(prefix.toLowerCase())) { proposals.add(new SCLCompletionProposal(decl.localName, decl.moduleName, SCLCompletionType.CONST, offset - prefix.length(), prefix)); } } } Collections.sort(proposals, COMPARATOR); moduleProposalCache = proposals; proposalCache = moduleProposalCache.toArray(new SCLCompletionProposal[moduleProposalCache.size()]); return proposalCache; } private ArrayList processRelativeImports(List relativeImports) { ArrayList absoluteImports = new ArrayList(relativeImports.size()); for(ImportDeclaration relativeImport : relativeImports) { if(relativeImport.moduleName.startsWith(".")) { String absoluteModuleName = convertRelativeModulePath(relativeImport.moduleName); if(absoluteModuleName != null) absoluteImports.add(new ImportDeclaration( absoluteModuleName, relativeImport.localName, relativeImport.reexport, relativeImport.spec)); } else absoluteImports.add(relativeImport); } return absoluteImports; } private String convertRelativeModulePath(String relativeModuleName) { String originalRelativeModuleName = relativeModuleName; int p = moduleName.lastIndexOf('/'); String parentPackage = p < 0 ? "" : moduleName.substring(0, p); while(relativeModuleName.startsWith(".")) { if(relativeModuleName.startsWith("./")) { relativeModuleName = relativeModuleName.substring(2); } else if(relativeModuleName.startsWith("../")) { relativeModuleName = relativeModuleName.substring(3); if(parentPackage.isEmpty()) { System.err.println("Couldn't resolve the relative module name " + originalRelativeModuleName + " when the current module name is " + moduleName + "."); return null; } p = parentPackage.lastIndexOf('/'); parentPackage = p < 0 ? "" : parentPackage.substring(0, p); } else { System.err.println("Couldn't resolve the relative module name " + originalRelativeModuleName + ". It has an invalid syntax."); return null; } } return parentPackage + "/" + relativeModuleName; } private static final Comparator COMPARATOR = new Comparator() { @Override public int compare(SCLCompletionProposal prop1, SCLCompletionProposal prop2) { if (prop1.isPrivate() && !prop2.isPrivate()) return -1; else if (!prop1.isPrivate() && prop2.isPrivate()) return 1; return prop1.getName().compareTo(prop2.getName()); } }; public SCLValue getValue(String text) { try { return Environments.getValue(env, text); } catch (AmbiguousNameException e) { // TODO could also return one of the conflicting alternatives return null; } } public String getHoverInfo(String text) { SCLValue value = getValue(text); if (value != null) return value.getDocumentation(); else return null; } }