package org.angularjs.service; import com.intellij.ide.highlighter.HtmlFileType; import com.intellij.ide.highlighter.XmlLikeFileType; import com.intellij.lang.javascript.psi.util.JSProjectUtil; import com.intellij.lang.javascript.service.JSLanguageServiceQueue; import com.intellij.lang.javascript.service.protocol.JSLanguageServiceCommand; import com.intellij.lang.javascript.service.protocol.JSLanguageServiceProtocol; import com.intellij.lang.javascript.service.protocol.JSLanguageServiceSimpleCommand; import com.intellij.lang.typescript.compiler.TypeScriptCompilerService; import com.intellij.lang.typescript.compiler.TypeScriptCompilerSettings; import com.intellij.lang.typescript.compiler.languageService.TypeScriptServerServiceImpl; import com.intellij.lang.typescript.compiler.languageService.protocol.commands.TypeScriptCompletionsRequestArgs; import com.intellij.lang.typescript.compiler.languageService.protocol.commands.TypeScriptGetErrCommand; import com.intellij.lang.typescript.compiler.ui.TypeScriptServerServiceSettings; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.TransactionGuard; import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ProjectRootManager; import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Conditions; import com.intellij.openapi.util.Key; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiFile; import com.intellij.psi.impl.source.html.HtmlFileImpl; import com.intellij.psi.util.CachedValueProvider; import com.intellij.psi.util.CachedValuesManager; import com.intellij.psi.util.ParameterizedCachedValue; import com.intellij.psi.util.ParameterizedCachedValueProvider; import com.intellij.psi.xml.XmlFile; import com.intellij.util.Consumer; import org.angularjs.index.AngularIndexUtil; import org.angularjs.service.protocol.Angular2LanguageServiceProtocol; import org.angularjs.service.protocol.command.Angular2CompletionsCommand; import org.angularjs.service.protocol.command.Angular2GetHtmlErrorCommand; import org.angularjs.service.protocol.command.Angular2GetProjectHtmlErrCommand; import org.angularjs.settings.AngularSettings; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import static com.intellij.lang.typescript.compiler.TypeScriptLanguageServiceAnnotatorCheckerProvider.checkServiceIsAvailable; public class Angular2LanguageService extends TypeScriptServerServiceImpl { private static final ParameterizedCachedValueProvider<VirtualFile, Project> CACHE_SERVICE_PATH_PROVIDER = new ParameterizedCachedValueProvider<VirtualFile, Project>() { @Nullable @Override public CachedValueProvider.Result<VirtualFile> compute(Project project) { VirtualFile result = findServiceDirectoryImpl(project); return CachedValueProvider.Result.create(result, JSProjectUtil.FILE_SYSTEM_STRUCTURE_MODIFICATION_TRACKER); } }; public static final Key<ParameterizedCachedValue<VirtualFile, Project>> NG_SERVICE_PATH_KEY = Key.create("CACHED_NG_SERVICE_PATH"); private final Condition<VirtualFile> myFileFilter; public Angular2LanguageService(@NotNull Project project, @NotNull TypeScriptCompilerSettings settings) { super(project, settings, "Angular Console"); myFileFilter = Conditions .or(super.getAcceptableFilesFilter(), (el) -> el != null && el.isInLocalFileSystem() && el.getFileType() == HtmlFileType.INSTANCE); } @Nullable @Override protected JSLanguageServiceProtocol createProtocol(Consumer<?> readyConsumer) { ApplicationManager.getApplication().assertReadAccessAllowed(); VirtualFile directory = getServiceDirectory(myProject); if (directory == null) { return null; } String path = directory.getCanonicalPath(); if (path == null) { return null; } return new Angular2LanguageServiceProtocol(myProject, path, mySettings, readyConsumer); } public static VirtualFile getServiceDirectory(Project project) { return CachedValuesManager.getManager(project) .getParameterizedCachedValue(project, NG_SERVICE_PATH_KEY, CACHE_SERVICE_PATH_PROVIDER, false, project); } @Nullable private static VirtualFile findServiceDirectoryImpl(Project project) { for (VirtualFile file : ProjectRootManager.getInstance(project).getContentRoots()) { if (file.isInLocalFileSystem() && file.isDirectory()) { VirtualFile modules = file.findChild("node_modules"); if (modules != null) { VirtualFile angularPackage = modules.findChild("@angular"); if (angularPackage != null) { VirtualFile serviceDirectory = angularPackage.findChild("language-service"); if (serviceDirectory != null) { return serviceDirectory; } } } } } return null; } @NotNull @Override protected TypeScriptGetErrCommand createGetErrCommand(@NotNull VirtualFile file, @NotNull String path) { if (file.getFileType() == HtmlFileType.INSTANCE) { return new Angular2GetHtmlErrorCommand(path); } return super.createGetErrCommand(file, path); } @Override public boolean canHighlight(@NotNull PsiFile file) { if (file instanceof HtmlFileImpl) { return checkServiceIsAvailable(myProject, this, mySettings); } return super.canHighlight(file); } @NotNull @Override protected String getProcessName() { return "Angular"; } @NotNull @Override public Condition<VirtualFile> getAcceptableFilesFilter() { return myFileFilter; } @Override @NotNull protected JSLanguageServiceSimpleCommand createCompletionCommand(@NotNull TypeScriptCompletionsRequestArgs args, @NotNull VirtualFile virtualFile, @NotNull PsiFile file) { return file instanceof XmlFile || virtualFile.getFileType() instanceof XmlLikeFileType ? new Angular2CompletionsCommand(args) : super.createCompletionCommand(args, virtualFile, file); } @NotNull @Override protected JSLanguageServiceCommand createProjectCommand(@NotNull VirtualFile file, @NotNull String path) { FileType type = file.getFileType(); return type instanceof XmlLikeFileType ? new Angular2GetProjectHtmlErrCommand(path) : super.createProjectCommand(file, path); } @Nullable @Override protected JSLanguageServiceQueue createLanguageServiceQueue() { TypeScriptCompilerService defaultService = TypeScriptCompilerService.getDefaultService(myProject); if (defaultService.isServiceCreated()) { JSLanguageServiceQueue.LOGGER.info("Dispose default service by " + getProcessName()); //dispose old service TransactionGuard.submitTransaction(this, () -> defaultService.terminateStartedProcess(false)); } return super.createLanguageServiceQueue(); } public static boolean isEnabledAngularService(Project project) { return AngularSettings.get(project).isUseService() && AngularIndexUtil.hasAngularJS2(project) && getServiceDirectory(project) != null; } @Nullable @Override public TypeScriptServerServiceSettings getServiceSettings() { return AngularSettings.get(myProject); } }