diff options
Diffstat (limited to 'json/src/com/jetbrains/jsonSchema/widget/JsonSchemaStatusWidget.java')
-rw-r--r-- | json/src/com/jetbrains/jsonSchema/widget/JsonSchemaStatusWidget.java | 310 |
1 files changed, 310 insertions, 0 deletions
diff --git a/json/src/com/jetbrains/jsonSchema/widget/JsonSchemaStatusWidget.java b/json/src/com/jetbrains/jsonSchema/widget/JsonSchemaStatusWidget.java new file mode 100644 index 00000000..5ff023e9 --- /dev/null +++ b/json/src/com/jetbrains/jsonSchema/widget/JsonSchemaStatusWidget.java @@ -0,0 +1,310 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.jetbrains.jsonSchema.widget; + +import com.intellij.icons.AllIcons; +import com.intellij.json.JsonLanguage; +import com.intellij.lang.Language; +import com.intellij.openapi.actionSystem.CommonDataKeys; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.fileTypes.LanguageFileType; +import com.intellij.openapi.project.DumbService; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.popup.ListPopup; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.impl.http.FileDownloadingAdapter; +import com.intellij.openapi.vfs.impl.http.HttpVirtualFile; +import com.intellij.openapi.vfs.impl.http.RemoteFileInfo; +import com.intellij.openapi.wm.StatusBarWidget; +import com.intellij.openapi.wm.impl.status.EditorBasedStatusBarPopup; +import com.jetbrains.jsonSchema.JsonSchemaCatalogProjectConfiguration; +import com.jetbrains.jsonSchema.extension.*; +import com.jetbrains.jsonSchema.ide.JsonSchemaService; +import com.jetbrains.jsonSchema.impl.JsonSchemaConflictNotificationProvider; +import com.jetbrains.jsonSchema.impl.JsonSchemaServiceImpl; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +class JsonSchemaStatusWidget extends EditorBasedStatusBarPopup { + private static final String JSON_SCHEMA_BAR = "JSON: "; + private static final String JSON_SCHEMA_BAR_OTHER_FILES = "Schema: "; + private static final String JSON_SCHEMA_TOOLTIP = "JSON Schema: "; + private static final String JSON_SCHEMA_TOOLTIP_OTHER_FILES = "Validated by JSON Schema: "; + private final JsonSchemaService myService; + private static final String ID = "JSONSchemaSelector"; + + JsonSchemaStatusWidget(Project project) { + super(project); + myService = JsonSchemaService.Impl.get(project); + myService.registerRemoteUpdateCallback(myUpdateCallback); + myService.registerResetAction(myUpdateCallback); + } + + private final Runnable myUpdateCallback = this::update; + + private static class MyWidgetState extends WidgetState { + boolean warning = false; + MyWidgetState(String toolTip, String text, boolean actionEnabled) { + super(toolTip, text, actionEnabled); + } + + public boolean isWarning() { + return warning; + } + + public void setWarning(boolean warning) { + this.warning = warning; + this.setIcon(warning ? AllIcons.General.Warning : null); + } + } + + private boolean hasAccessToSymbols() { + return !DumbService.getInstance(myProject).isDumb(); + } + + @NotNull + @Override + protected WidgetState getWidgetState(@Nullable VirtualFile file) { + if (file == null) { + return WidgetState.HIDDEN; + } + + JsonSchemaEnabler[] enablers = JsonSchemaEnabler.EXTENSION_POINT_NAME.getExtensions(); + if (Arrays.stream(enablers).noneMatch(e -> e.isEnabledForFile(file) && e.shouldShowSwitcherWidget(file))) { + return WidgetState.HIDDEN; + } + + FileType fileType = file.getFileType(); + Language language = fileType instanceof LanguageFileType ? ((LanguageFileType)fileType).getLanguage() : null; + boolean isJsonFile = language instanceof JsonLanguage; + + if (!hasAccessToSymbols()) { + return WidgetState.getDumbModeState("JSON schema service", isJsonFile ? JSON_SCHEMA_BAR : JSON_SCHEMA_BAR_OTHER_FILES); + } + + JsonWidgetSuppressor[] suppressors = JsonWidgetSuppressor.EXTENSION_POINT_NAME.getExtensions(); + if (Arrays.stream(suppressors).anyMatch(s -> s.suppressSwitcherWidget(file, myProject))) { + return WidgetState.HIDDEN; + } + + Collection<VirtualFile> schemaFiles = myService.getSchemaFilesForFile(file); + if (schemaFiles.size() == 0) { + return getNoSchemaState(); + } + + if (schemaFiles.size() != 1) { + List<VirtualFile> onlyUserSchemas = schemaFiles.stream().filter(s -> { + JsonSchemaFileProvider provider = myService.getSchemaProvider(s); + return provider != null && provider.getSchemaType() == SchemaType.userSchema; + }).collect(Collectors.toList()); + if (onlyUserSchemas.size() > 1) { + MyWidgetState state = new MyWidgetState(JsonSchemaConflictNotificationProvider.createMessage(schemaFiles, myService, + "<br/>", "Conflicting schemas:<br/>", + ""), + schemaFiles.size() + " schemas (!)", true); + state.setWarning(true); + return state; + } + schemaFiles = onlyUserSchemas; + if (schemaFiles.size() == 0) { + return getNoSchemaState(); + } + } + + VirtualFile schemaFile = schemaFiles.iterator().next(); + schemaFile = ((JsonSchemaServiceImpl)myService).replaceHttpFileWithBuiltinIfNeeded(schemaFile); + + String tooltip = isJsonFile ? JSON_SCHEMA_TOOLTIP : JSON_SCHEMA_TOOLTIP_OTHER_FILES; + String bar = isJsonFile ? JSON_SCHEMA_BAR : JSON_SCHEMA_BAR_OTHER_FILES; + + if (schemaFile instanceof HttpVirtualFile) { + RemoteFileInfo info = ((HttpVirtualFile)schemaFile).getFileInfo(); + if (info == null) return getDownloadErrorState(null); + + //noinspection EnumSwitchStatementWhichMissesCases + switch (info.getState()) { + case DOWNLOADING_NOT_STARTED: + addDownloadingUpdateListener(info); + return new MyWidgetState(tooltip + getSchemaFileDesc(schemaFile), bar + getPresentableNameForFile(schemaFile), + true); + case DOWNLOADING_IN_PROGRESS: + addDownloadingUpdateListener(info); + return new MyWidgetState("Download is scheduled or in progress", "Downloading JSON schema", false); + case ERROR_OCCURRED: + return getDownloadErrorState(info.getErrorMessage()); + } + } + + if (!isValidSchemaFile(schemaFile)) { + MyWidgetState state = new MyWidgetState("File is not a schema", "JSON schema error", true); + state.setWarning(true); + return state; + } + + JsonSchemaFileProvider provider = myService.getSchemaProvider(schemaFile); + if (provider != null) { + final boolean preferRemoteSchemas = JsonSchemaCatalogProjectConfiguration.getInstance(myProject).isPreferRemoteSchemas(); + final String remoteSource = provider.getRemoteSource(); + String providerName = preferRemoteSchemas && remoteSource != null && !remoteSource.endsWith("!") ? remoteSource : provider.getPresentableName(); + String shortName = StringUtil.trimEnd(StringUtil.trimEnd(providerName, ".json"), "-schema"); + String name = preferRemoteSchemas && remoteSource != null && !remoteSource.endsWith("!") ? bar + new JsonSchemaInfo(remoteSource).getDescription() + : (shortName.startsWith("JSON schema") ? shortName : (bar + shortName)); + String kind = !preferRemoteSchemas && (provider.getSchemaType() == SchemaType.embeddedSchema || provider.getSchemaType() == SchemaType.schema) + ? " (bundled)" + : ""; + return new MyWidgetState(tooltip + providerName + kind, name, true); + } + + return new MyWidgetState(tooltip + getSchemaFileDesc(schemaFile), bar + getPresentableNameForFile(schemaFile), + true); + } + + private void addDownloadingUpdateListener(@NotNull RemoteFileInfo info) { + info.addDownloadingListener(new FileDownloadingAdapter() { + @Override + public void fileDownloaded(@NotNull VirtualFile localFile) { + update(); + } + + @Override + public void errorOccurred(@NotNull String errorMessage) { + update(); + } + + @Override + public void downloadingCancelled() { + update(); + } + }); + } + + private boolean isValidSchemaFile(@Nullable VirtualFile schemaFile) { + return schemaFile != null && myService.isSchemaFile(schemaFile) && myService.isApplicableToFile(schemaFile); + } + + @Nullable + private static String extractNpmPackageName(@Nullable String path) { + if (path == null) return null; + int idx = path.indexOf("node_modules"); + if (idx != -1) { + int trimIndex = idx + "node_modules".length() + 1; + if (trimIndex < path.length()) { + path = path.substring(trimIndex); + idx = StringUtil.indexOfAny(path, "\\/"); + if (idx != -1) { + if (path.startsWith("@")) { + idx = StringUtil.indexOfAny(path, "\\/", idx + 1, path.length()); + } + } + + if (idx != -1) { + return path.substring(0, idx); + } + } + } + return null; + } + + @NotNull + private static String getPresentableNameForFile(@NotNull VirtualFile schemaFile) { + if (schemaFile instanceof HttpVirtualFile) { + return new JsonSchemaInfo(schemaFile.getUrl()).getDescription(); + } + + String nameWithoutExtension = schemaFile.getNameWithoutExtension(); + if (!JsonSchemaInfo.isVeryDumbName(nameWithoutExtension)) return nameWithoutExtension; + + String path = schemaFile.getPath(); + + String npmPackageName = extractNpmPackageName(path); + return npmPackageName != null ? npmPackageName : schemaFile.getName(); + } + + @NotNull + private static WidgetState getDownloadErrorState(@Nullable String message) { + MyWidgetState state = new MyWidgetState("Error downloading schema" + (message == null ? "" : (": <br/>" + message)), + "JSON schema error", true); + state.setWarning(true); + return state; + } + + @NotNull + private static WidgetState getNoSchemaState() { + return new MyWidgetState("No JSON Schema defined", "No JSON schema", true); + } + + @NotNull + private static String getSchemaFileDesc(@NotNull VirtualFile schemaFile) { + if (schemaFile instanceof HttpVirtualFile) { + return schemaFile.getPresentableUrl(); + } + + String npmPackageName = extractNpmPackageName(schemaFile.getPath()); + return schemaFile.getName() + (npmPackageName == null ? "" : (" (Package: " + npmPackageName + ")")); + } + + @Nullable + @Override + protected ListPopup createPopup(DataContext context) { + final VirtualFile virtualFile = CommonDataKeys.VIRTUAL_FILE.getData(context); + if (virtualFile == null) return null; + + Project project = getProject(); + if (project == null) return null; + WidgetState state = getWidgetState(virtualFile); + if (!(state instanceof MyWidgetState)) return null; + return doCreatePopup(virtualFile, project, ((MyWidgetState)state).isWarning()); + } + + @NotNull + private ListPopup doCreatePopup(@NotNull VirtualFile virtualFile, @NotNull Project project, boolean showOnlyEdit) { + return JsonSchemaStatusPopup.createPopup(myService, project, virtualFile, showOnlyEdit); + } + + @Override + protected void registerCustomListeners() { + class Listener implements DumbService.DumbModeListener { + volatile boolean isDumbMode; + + @Override + public void enteredDumbMode() { + isDumbMode = true; + update(); + } + + @Override + public void exitDumbMode() { + isDumbMode = false; + update(); + } + } + + Listener listener = new Listener(); + myConnection.subscribe(DumbService.DUMB_MODE, listener); + } + + @NotNull + @Override + protected StatusBarWidget createInstance(Project project) { + return new JsonSchemaStatusWidget(project); + } + + @NotNull + @Override + public String ID() { + return ID; + } + + @Override + public void dispose() { + myService.unregisterRemoteUpdateCallback(myUpdateCallback); + myService.unregisterResetAction(myUpdateCallback); + super.dispose(); + } +} |