diff options
Diffstat (limited to 'json/src/com/jetbrains/jsonSchema/impl/JsonSchemaCompletionContributor.java')
-rw-r--r-- | json/src/com/jetbrains/jsonSchema/impl/JsonSchemaCompletionContributor.java | 672 |
1 files changed, 672 insertions, 0 deletions
diff --git a/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaCompletionContributor.java b/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaCompletionContributor.java new file mode 100644 index 00000000..cbe848a6 --- /dev/null +++ b/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaCompletionContributor.java @@ -0,0 +1,672 @@ +// 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.impl; + +import com.intellij.codeInsight.AutoPopupController; +import com.intellij.codeInsight.completion.*; +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.codeInsight.lookup.LookupElementBuilder; +import com.intellij.icons.AllIcons; +import com.intellij.ide.DataManager; +import com.intellij.internal.statistic.service.fus.collectors.FUSApplicationUsageTrigger; +import com.intellij.json.psi.*; +import com.intellij.openapi.actionSystem.IdeActions; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.editor.Caret; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.EditorModificationUtil; +import com.intellij.openapi.editor.SelectionModel; +import com.intellij.openapi.editor.actionSystem.CaretSpecificDataContext; +import com.intellij.openapi.editor.actionSystem.EditorActionHandler; +import com.intellij.openapi.editor.actionSystem.EditorActionManager; +import com.intellij.openapi.editor.actions.EditorActionUtil; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.impl.http.HttpVirtualFile; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiElement; +import com.intellij.psi.TokenType; +import com.intellij.psi.codeStyle.CodeStyleManager; +import com.intellij.psi.impl.source.tree.LeafPsiElement; +import com.intellij.psi.util.PsiUtilCore; +import com.intellij.util.Consumer; +import com.intellij.util.ObjectUtils; +import com.intellij.util.ThreeState; +import com.intellij.util.containers.ContainerUtil; +import com.jetbrains.jsonSchema.extension.JsonLikePsiWalker; +import com.jetbrains.jsonSchema.extension.JsonSchemaFileProvider; +import com.jetbrains.jsonSchema.extension.SchemaType; +import com.jetbrains.jsonSchema.extension.adapters.JsonObjectValueAdapter; +import com.jetbrains.jsonSchema.extension.adapters.JsonPropertyAdapter; +import com.jetbrains.jsonSchema.ide.JsonSchemaService; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.TestOnly; + +import javax.swing.*; +import java.util.*; +import java.util.stream.Collectors; + +/** + * @author Irina.Chernushina on 10/1/2015. + */ +public class JsonSchemaCompletionContributor extends CompletionContributor { + private static final String BUILTIN_USAGE_KEY = "json.schema.builtin.completion"; + private static final String SCHEMA_USAGE_KEY = "json.schema.schema.completion"; + private static final String USER_USAGE_KEY = "json.schema.user.completion"; + private static final String REMOTE_USAGE_KEY = "json.schema.remote.completion"; + + @Override + public void fillCompletionVariants(@NotNull CompletionParameters parameters, @NotNull CompletionResultSet result) { + final PsiElement position = parameters.getPosition(); + final VirtualFile file = PsiUtilCore.getVirtualFile(position); + if (file == null) return; + + final JsonSchemaService service = JsonSchemaService.Impl.get(position.getProject()); + if (!service.isApplicableToFile(file)) return; + final JsonSchemaObject rootSchema = service.getSchemaObject(file); + if (rootSchema == null) return; + PsiElement positionParent = position.getParent(); + if (positionParent != null) { + PsiElement parent = positionParent.getParent(); + if (parent instanceof JsonProperty && "$ref".equals(((JsonProperty)parent).getName()) && service.isSchemaFile(file)) { + return; + } + } + + final VirtualFile schemaFile = rootSchema.getSchemaFile(); + updateStat(service.getSchemaProvider(schemaFile), schemaFile); + doCompletion(parameters, result, rootSchema); + } + + public static void doCompletion(@NotNull final CompletionParameters parameters, + @NotNull final CompletionResultSet result, + @NotNull final JsonSchemaObject rootSchema) { + doCompletion(parameters, result, rootSchema, true); + } + + public static void doCompletion(@NotNull final CompletionParameters parameters, + @NotNull final CompletionResultSet result, + @NotNull final JsonSchemaObject rootSchema, + boolean stop) { + final PsiElement completionPosition = parameters.getOriginalPosition() != null ? parameters.getOriginalPosition() : + parameters.getPosition(); + new Worker(rootSchema, parameters.getPosition(), completionPosition, result).work(); + if (stop) { + result.stopHere(); + } + } + + @TestOnly + @NotNull + public static List<LookupElement> getCompletionVariants(@NotNull final JsonSchemaObject schema, + @NotNull final PsiElement position, @NotNull final PsiElement originalPosition) { + final List<LookupElement> result = new ArrayList<>(); + new Worker(schema, position, originalPosition, element -> result.add(element)).work(); + return result; + } + + private static void updateStat(@Nullable JsonSchemaFileProvider provider, VirtualFile schemaFile) { + if (provider == null) { + if (schemaFile instanceof HttpVirtualFile) { + // auto-detected and auto-downloaded JSON schemas + FUSApplicationUsageTrigger usageTrigger = FUSApplicationUsageTrigger.getInstance(); + usageTrigger.trigger(JsonSchemaUsageTriggerCollector.class, REMOTE_USAGE_KEY); + } + return; + } + final SchemaType schemaType = provider.getSchemaType(); + FUSApplicationUsageTrigger usageTrigger = FUSApplicationUsageTrigger.getInstance(); + switch (schemaType) { + case schema: + usageTrigger.trigger(JsonSchemaUsageTriggerCollector.class, SCHEMA_USAGE_KEY); + break; + case userSchema: + usageTrigger.trigger(JsonSchemaUsageTriggerCollector.class, USER_USAGE_KEY); + break; + case embeddedSchema: + usageTrigger.trigger(JsonSchemaUsageTriggerCollector.class, BUILTIN_USAGE_KEY); + break; + case remoteSchema: + // this works only for user-specified remote schemas in our settings, but not for auto-detected remote schemas + usageTrigger.trigger(JsonSchemaUsageTriggerCollector.class, REMOTE_USAGE_KEY); + break; + } + } + + private static class Worker { + @NotNull private final JsonSchemaObject myRootSchema; + @NotNull private final PsiElement myPosition; + @NotNull private final PsiElement myOriginalPosition; + @NotNull private final Consumer<LookupElement> myResultConsumer; + private final boolean myWrapInQuotes; + private final boolean myInsideStringLiteral; + // we need this set to filter same-named suggestions (they can be suggested by several matching schemes) + private final Set<LookupElement> myVariants; + private final JsonLikePsiWalker myWalker; + + Worker(@NotNull JsonSchemaObject rootSchema, @NotNull PsiElement position, + @NotNull PsiElement originalPosition, @NotNull final Consumer<LookupElement> resultConsumer) { + myRootSchema = rootSchema; + myPosition = position; + myOriginalPosition = originalPosition; + myResultConsumer = resultConsumer; + myVariants = new HashSet<>(); + myWalker = JsonLikePsiWalker.getWalker(myPosition, myRootSchema); + myWrapInQuotes = myWalker != null && myWalker.isNameQuoted() && !(position.getParent() instanceof JsonStringLiteral); + myInsideStringLiteral = position.getParent() instanceof JsonStringLiteral; + } + + public void work() { + if (myWalker == null) return; + final PsiElement checkable = myWalker.goUpToCheckable(myPosition); + if (checkable == null) return; + final ThreeState isName = myWalker.isName(checkable); + final List<JsonSchemaVariantsTreeBuilder.Step> position = myWalker.findPosition(checkable, isName == ThreeState.NO); + if (position == null || position.isEmpty() && isName == ThreeState.NO) return; + + final Collection<JsonSchemaObject> schemas = new JsonSchemaResolver(myRootSchema, false, position).resolve(); + final Set<String> knownNames = ContainerUtil.newHashSet(); + // too long here, refactor further + schemas.forEach(schema -> { + if (isName != ThreeState.NO) { + final boolean insertComma = myWalker.hasPropertiesBehindAndNoComma(myPosition); + final boolean hasValue = myWalker.isPropertyWithValue(checkable); + + final Collection<String> properties = myWalker.getPropertyNamesOfParentObject(myOriginalPosition, myPosition); + final JsonPropertyAdapter adapter = myWalker.getParentPropertyAdapter(myOriginalPosition); + + final Map<String, JsonSchemaObject> schemaProperties = schema.getProperties(); + addAllPropertyVariants(insertComma, hasValue, properties, adapter, schemaProperties, knownNames); + addIfThenElsePropertyNameVariants(schema, insertComma, hasValue, properties, adapter, knownNames); + } + + if (isName != ThreeState.YES) { + suggestValues(schema, isName == ThreeState.NO); + } + }); + + for (LookupElement variant : myVariants) { + myResultConsumer.consume(variant); + } + } + + private void addIfThenElsePropertyNameVariants(@NotNull JsonSchemaObject schema, + boolean insertComma, + boolean hasValue, + @NotNull Collection<String> properties, + @Nullable JsonPropertyAdapter adapter, + Set<String> knownNames) { + if (schema.getIf() == null) return; + + JsonLikePsiWalker walker = JsonLikePsiWalker.getWalker(myPosition, schema); + JsonPropertyAdapter propertyAdapter = walker == null ? null : walker.getParentPropertyAdapter(myPosition); + if (propertyAdapter == null) return; + + JsonObjectValueAdapter object = propertyAdapter.getParentObject(); + if (object == null) return; + + JsonSchemaAnnotatorChecker checker = new JsonSchemaAnnotatorChecker(JsonComplianceCheckerOptions.RELAX_ENUM_CHECK); + checker.checkByScheme(object, schema.getIf()); + if (checker.isCorrect()) { + JsonSchemaObject then = schema.getThen(); + if (then != null) { + addAllPropertyVariants(insertComma, hasValue, properties, adapter, then.getProperties(), knownNames); + } + } + else { + JsonSchemaObject schemaElse = schema.getElse(); + if (schemaElse != null) { + addAllPropertyVariants(insertComma, hasValue, properties, adapter, schemaElse.getProperties(), knownNames); + } + } + } + + private void addAllPropertyVariants(boolean insertComma, + boolean hasValue, + Collection<String> properties, + JsonPropertyAdapter adapter, + Map<String, JsonSchemaObject> schemaProperties, Set<String> knownNames) { + schemaProperties.keySet().stream() + .filter(name -> !properties.contains(name) && !knownNames.contains(name) || adapter != null && name.equals(adapter.getName())) + .forEach(name -> {knownNames.add(name); addPropertyVariant(name, schemaProperties.get(name), hasValue, insertComma);}); + } + + private void suggestValues(JsonSchemaObject schema, boolean isSurelyValue) { + suggestValuesForSchemaVariants(schema.getAnyOf(), isSurelyValue); + suggestValuesForSchemaVariants(schema.getOneOf(), isSurelyValue); + suggestValuesForSchemaVariants(schema.getAllOf(), isSurelyValue); + + if (schema.getEnum() != null) { + for (Object o : schema.getEnum()) { + if (myInsideStringLiteral && !(o instanceof String)) continue; + addValueVariant(o.toString(), null); + } + } + else if (isSurelyValue) { + final JsonSchemaType type = schema.guessType(); + suggestSpecialValues(type); + if (type != null) { + suggestByType(schema, type); + } else if (schema.getTypeVariants() != null) { + for (JsonSchemaType schemaType : schema.getTypeVariants()) { + suggestByType(schema, schemaType); + } + } + } + } + + private void suggestSpecialValues(@Nullable JsonSchemaType type) { + if (JsonSchemaVersion.isSchemaSchemaId(myRootSchema.getId()) && type == JsonSchemaType._string) { + JsonPropertyAdapter propertyAdapter = myWalker.getParentPropertyAdapter(myOriginalPosition); + if (propertyAdapter == null || !"required".equals(propertyAdapter.getName())) return; + PsiElement checkable = myWalker.goUpToCheckable(myPosition); + if (!(checkable instanceof JsonStringLiteral) && !(checkable instanceof JsonReferenceExpression)) return; + JsonObject propertiesObject = JsonRequiredPropsReferenceProvider.findPropertiesObject(checkable); + if (propertiesObject == null) return; + PsiElement parent = checkable.getParent(); + Set<String> items = parent instanceof JsonArray ? ((JsonArray)parent).getValueList().stream() + .filter(v -> v instanceof JsonStringLiteral).map(v -> ((JsonStringLiteral)v).getValue()).collect(Collectors.toSet()) : ContainerUtil.newHashSet(); + propertiesObject.getPropertyList().stream().map(p -> p.getName()).filter(n -> !items.contains(n)).forEach(n -> addStringVariant(n)); + } + } + + private void suggestByType(JsonSchemaObject schema, JsonSchemaType type) { + if (JsonSchemaType._string.equals(type)) { + addPossibleStringValue(schema); + } + if (myInsideStringLiteral){ + return; + } + if (JsonSchemaType._boolean.equals(type)) { + addPossibleBooleanValue(type); + } else if (JsonSchemaType._null.equals(type)) { + addValueVariant("null", null); + } else if (JsonSchemaType._array.equals(type)) { + String value = myWalker.getDefaultArrayValue(); + addValueVariant(value, null, + myWalker.defaultArrayValueDescription(), createArrayOrObjectLiteralInsertHandler(myWalker.invokeEnterBeforeObjectAndArray(), value.length())); + } else if (JsonSchemaType._object.equals(type)) { + String value = myWalker.getDefaultObjectValue(); + addValueVariant(value, null, + myWalker.defaultObjectValueDescription(), createArrayOrObjectLiteralInsertHandler(myWalker.invokeEnterBeforeObjectAndArray(), value.length())); + } + } + + private void addPossibleStringValue(JsonSchemaObject schema) { + Object defaultValue = schema.getDefault(); + String defaultValueString = defaultValue == null ? null : defaultValue.toString(); + addStringVariant(defaultValueString); + } + + private void addStringVariant(String defaultValueString) { + if (!StringUtil.isEmpty(defaultValueString)) { + String normalizedValue = defaultValueString; + boolean shouldQuote = myWalker.quotesForStringLiterals(); + boolean isQuoted = StringUtil.isQuotedString(normalizedValue); + if (shouldQuote && !isQuoted) { + normalizedValue = StringUtil.wrapWithDoubleQuote(normalizedValue); + } + else if (!shouldQuote && isQuoted) { + normalizedValue = StringUtil.unquoteString(normalizedValue); + } + addValueVariant(normalizedValue, null); + } + } + + private void suggestValuesForSchemaVariants(List<JsonSchemaObject> list, boolean isSurelyValue) { + if (list != null && list.size() > 0) { + for (JsonSchemaObject schemaObject : list) { + suggestValues(schemaObject, isSurelyValue); + } + } + } + + private void addPossibleBooleanValue(JsonSchemaType type) { + if (JsonSchemaType._boolean.equals(type)) { + addValueVariant("true", null); + addValueVariant("false", null); + } + } + + + private void addValueVariant(@NotNull String key, @SuppressWarnings("SameParameterValue") @Nullable final String description) { + addValueVariant(key, description, null, null); + } + + private void addValueVariant(@NotNull String key, + @SuppressWarnings("SameParameterValue") @Nullable final String description, + @Nullable final String altText, + @Nullable InsertHandler<LookupElement> handler) { + LookupElementBuilder builder = LookupElementBuilder.create(!myWrapInQuotes ? StringUtil.unquoteString(key) : key); + if (altText != null) { + builder = builder.withPresentableText(altText); + } + if (description != null) { + builder = builder.withTypeText(description); + } + if (handler != null) { + builder = builder.withInsertHandler(handler); + } + myVariants.add(builder); + } + + private void addPropertyVariant(@NotNull String key, @NotNull JsonSchemaObject jsonSchemaObject, boolean hasValue, boolean insertComma) { + final Collection<JsonSchemaObject> variants = new JsonSchemaResolver(jsonSchemaObject).resolve(); + jsonSchemaObject = ObjectUtils.coalesce(ContainerUtil.getFirstItem(variants), jsonSchemaObject); + key = !myWrapInQuotes ? key : StringUtil.wrapWithDoubleQuote(key); + LookupElementBuilder builder = LookupElementBuilder.create(key); + + final String typeText = JsonSchemaDocumentationProvider.getBestDocumentation(true, jsonSchemaObject); + if (!StringUtil.isEmptyOrSpaces(typeText)) { + final String text = StringUtil.removeHtmlTags(typeText); + final int firstSentenceMark = text.indexOf(". "); + builder = builder.withTypeText(firstSentenceMark == -1 ? text : text.substring(0, firstSentenceMark + 1), true); + } + else { + String type = jsonSchemaObject.getTypeDescription(true); + if (type != null) { + builder = builder.withTypeText(type, true); + } + } + + builder = builder.withIcon(getIcon(jsonSchemaObject.guessType())); + + if (hasSameType(variants)) { + final JsonSchemaType type = jsonSchemaObject.guessType(); + final List<Object> values = jsonSchemaObject.getEnum(); + Object defaultValue = jsonSchemaObject.getDefault(); + + boolean hasValues = !ContainerUtil.isEmpty(values); + if (type != null || hasValues || defaultValue != null) { + builder = builder.withInsertHandler( + !hasValues || values.stream().map(v -> v.getClass()).distinct().count() == 1 ? + createPropertyInsertHandler(jsonSchemaObject, hasValue, insertComma) : + createDefaultPropertyInsertHandler(true, insertComma)); + } + else { + builder = builder.withInsertHandler(createDefaultPropertyInsertHandler(false, insertComma)); + } + } else if (!hasValue) { + builder = builder.withInsertHandler(createDefaultPropertyInsertHandler(false, insertComma)); + } + + myVariants.add(builder); + } + + @NotNull + private static Icon getIcon(@Nullable JsonSchemaType type) { + if (type == null) return AllIcons.Nodes.Property; + switch (type) { + case _object: + return AllIcons.Json.Object; + case _array: + return AllIcons.Json.Array; + default: + return AllIcons.Nodes.Property; + } + } + + private static boolean hasSameType(@NotNull Collection<JsonSchemaObject> variants) { + return variants.stream().map(JsonSchemaObject::guessType).filter(Objects::nonNull).distinct().count() <= 1; + } + + private static InsertHandler<LookupElement> createArrayOrObjectLiteralInsertHandler(boolean newline, int insertedTextSize) { + return new InsertHandler<LookupElement>() { + @Override + public void handleInsert(@NotNull InsertionContext context, @NotNull LookupElement item) { + Editor editor = context.getEditor(); + + if (!newline) { + EditorModificationUtil.moveCaretRelatively(editor, -1); + } + else { + EditorModificationUtil.moveCaretRelatively(editor, -insertedTextSize); + PsiDocumentManager.getInstance(context.getProject()).commitDocument(editor.getDocument()); + invokeEnterHandler(editor); + EditorActionUtil.moveCaretToLineEnd(editor, false, false); + } + AutoPopupController.getInstance(context.getProject()).autoPopupMemberLookup(editor, null); + } + }; + } + + private InsertHandler<LookupElement> createDefaultPropertyInsertHandler(@SuppressWarnings("SameParameterValue") boolean hasValue, + boolean insertComma) { + return new InsertHandler<LookupElement>() { + @Override + public void handleInsert(@NotNull InsertionContext context, @NotNull LookupElement item) { + ApplicationManager.getApplication().assertWriteAccessAllowed(); + Editor editor = context.getEditor(); + Project project = context.getProject(); + + if (handleInsideQuotesInsertion(context, editor, hasValue)) return; + + // inserting longer string for proper formatting + final String stringToInsert = ": 1" + (insertComma ? "," : ""); + EditorModificationUtil.insertStringAtCaret(editor, stringToInsert, false, true, 2); + formatInsertedString(context, stringToInsert.length()); + final int offset = editor.getCaretModel().getOffset(); + context.getDocument().deleteString(offset, offset + 1); + PsiDocumentManager.getInstance(project).commitDocument(editor.getDocument()); + AutoPopupController.getInstance(context.getProject()).autoPopupMemberLookup(context.getEditor(), null); + } + }; + } + + @NotNull + private InsertHandler<LookupElement> createPropertyInsertHandler(@NotNull JsonSchemaObject jsonSchemaObject, + final boolean hasValue, + boolean insertComma) { + JsonSchemaType type = jsonSchemaObject.guessType(); + List<Object> values = jsonSchemaObject.getEnum(); + if (type == null && values != null && !values.isEmpty()) type = detectType(values); + final Object defaultValue = jsonSchemaObject.getDefault(); + final String defaultValueAsString = defaultValue == null || defaultValue instanceof JsonSchemaObject ? null : + (defaultValue instanceof String ? "\"" + defaultValue + "\"" : + String.valueOf(defaultValue)); + JsonSchemaType finalType = type; + return new InsertHandler<LookupElement>() { + @Override + public void handleInsert(@NotNull InsertionContext context, @NotNull LookupElement item) { + ApplicationManager.getApplication().assertWriteAccessAllowed(); + Editor editor = context.getEditor(); + Project project = context.getProject(); + String stringToInsert = null; + final String comma = insertComma ? "," : ""; + + if (handleInsideQuotesInsertion(context, editor, hasValue)) return; + + PsiElement element = context.getFile().findElementAt(editor.getCaretModel().getOffset()); + boolean insertColon = element == null || !":".equals(element.getText()); + if (!insertColon) { + editor.getCaretModel().moveToOffset(editor.getCaretModel().getOffset() + 1); + } + + if (finalType != null) { + boolean hadEnter; + switch (finalType) { + case _object: + EditorModificationUtil.insertStringAtCaret(editor, insertColon ? ": " : " ", + false, true, + insertColon ? 2 : 1); + hadEnter = false; + boolean invokeEnter = myWalker.invokeEnterBeforeObjectAndArray(); + if (insertColon && invokeEnter) { + invokeEnterHandler(editor); + hadEnter = true; + } + if (insertColon) { + stringToInsert = myWalker.getDefaultObjectValue() + comma; + EditorModificationUtil.insertStringAtCaret(editor, stringToInsert, + false, true, + hadEnter ? 0 : 1); + } + + if (hadEnter || !insertColon) { + EditorActionUtil.moveCaretToLineEnd(editor, false, false); + } + + PsiDocumentManager.getInstance(project).commitDocument(editor.getDocument()); + if (!hadEnter && stringToInsert != null) { + formatInsertedString(context, stringToInsert.length()); + } + if (stringToInsert != null && !invokeEnter) { + invokeEnterHandler(editor); + } + break; + case _boolean: + String value = String.valueOf(Boolean.TRUE.toString().equals(defaultValueAsString)); + stringToInsert = (insertColon ? ": " : " ") + value + comma; + SelectionModel model = editor.getSelectionModel(); + + EditorModificationUtil.insertStringAtCaret(editor, stringToInsert, + false, true, + stringToInsert.length() - comma.length()); + formatInsertedString(context, stringToInsert.length()); + int start = editor.getSelectionModel().getSelectionStart(); + model.setSelection(start - value.length(), start); + AutoPopupController.getInstance(context.getProject()).autoPopupMemberLookup(context.getEditor(), null); + break; + case _array: + EditorModificationUtil.insertStringAtCaret(editor, insertColon ? ": " : " ", + false, true, + insertColon ? 2 : 1); + hadEnter = false; + if (insertColon && myWalker.invokeEnterBeforeObjectAndArray()) { + invokeEnterHandler(editor); + hadEnter = true; + } + if (insertColon) { + stringToInsert = myWalker.getDefaultArrayValue() + comma; + EditorModificationUtil.insertStringAtCaret(editor, stringToInsert, + false, true, + hadEnter ? 0 : 1); + } + if (hadEnter) { + EditorActionUtil.moveCaretToLineEnd(editor, false, false); + } + + PsiDocumentManager.getInstance(project).commitDocument(editor.getDocument()); + + if (stringToInsert != null) { + formatInsertedString(context, stringToInsert.length()); + } + break; + case _string: + case _integer: + insertPropertyWithEnum(context, editor, defaultValueAsString, values, finalType, comma, myWalker, insertColon); + break; + default: + } + } + else { + insertPropertyWithEnum(context, editor, defaultValueAsString, values, null, comma, myWalker, insertColon); + } + } + }; + } + + private static void invokeEnterHandler(Editor editor) { + EditorActionHandler handler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_ENTER); + Caret caret = editor.getCaretModel().getCurrentCaret(); + handler.execute(editor, caret, + new CaretSpecificDataContext(DataManager.getInstance().getDataContext(editor.getContentComponent()), caret)); + } + + private boolean handleInsideQuotesInsertion(@NotNull InsertionContext context, @NotNull Editor editor, boolean hasValue) { + if (myInsideStringLiteral) { + int offset = editor.getCaretModel().getOffset(); + PsiElement element = context.getFile().findElementAt(offset); + int tailOffset = context.getTailOffset(); + int guessEndOffset = tailOffset + 1; + if (element instanceof LeafPsiElement) { + if (handleIncompleteString(editor, element)) return false; + int endOffset = element.getTextRange().getEndOffset(); + if (endOffset > tailOffset) { + context.getDocument().deleteString(tailOffset, endOffset - 1); + } + } + if (hasValue) { + return true; + } + editor.getCaretModel().moveToOffset(guessEndOffset); + } else editor.getCaretModel().moveToOffset(context.getTailOffset()); + return false; + } + + private static boolean handleIncompleteString(@NotNull Editor editor, @NotNull PsiElement element) { + if (((LeafPsiElement)element).getElementType() == TokenType.WHITE_SPACE) { + PsiElement prevSibling = element.getPrevSibling(); + if (prevSibling instanceof JsonProperty) { + JsonValue nameElement = ((JsonProperty)prevSibling).getNameElement(); + if (!nameElement.getText().endsWith("\"")) { + editor.getCaretModel().moveToOffset(nameElement.getTextRange().getEndOffset()); + EditorModificationUtil.insertStringAtCaret(editor, "\"", false, true, 1); + return true; + } + } + } + return false; + } + + @Nullable + private static JsonSchemaType detectType(List<Object> values) { + JsonSchemaType type = null; + for (Object value : values) { + JsonSchemaType newType = null; + if (value instanceof Integer) newType = JsonSchemaType._integer; + if (type != null && !type.equals(newType)) return null; + type = newType; + } + return type; + } + } + + private static void insertPropertyWithEnum(InsertionContext context, + Editor editor, + String defaultValue, + List<Object> values, + JsonSchemaType type, + String comma, + JsonLikePsiWalker walker, + boolean insertColon) { + if (!walker.quotesForStringLiterals() && defaultValue != null) { + defaultValue = StringUtil.unquoteString(defaultValue); + } + final boolean isNumber = type != null && (JsonSchemaType._integer.equals(type) || JsonSchemaType._number.equals(type)) || + type == null && (defaultValue != null && + !StringUtil.isQuotedString(defaultValue) || values != null && ContainerUtil.and(values, v -> !(v instanceof String))); + boolean hasValues = !ContainerUtil.isEmpty(values); + boolean hasDefaultValue = !StringUtil.isEmpty(defaultValue); + boolean hasQuotes = isNumber || !walker.quotesForStringLiterals(); + final String colonWs = insertColon ? ": " : " "; + String stringToInsert = colonWs + (hasDefaultValue ? defaultValue : (hasQuotes ? "" : "\"\"")) + comma; + EditorModificationUtil.insertStringAtCaret(editor, stringToInsert, false, true, + insertColon ? 2 : 1); + if (!hasQuotes || hasDefaultValue) { + SelectionModel model = editor.getSelectionModel(); + int caretStart = model.getSelectionStart(); + int newOffset = caretStart + (hasDefaultValue ? defaultValue.length() : 1); + if (hasDefaultValue && !hasQuotes) newOffset--; + model.setSelection(hasQuotes ? caretStart : (caretStart + 1), newOffset); + editor.getCaretModel().moveToOffset(newOffset); + } + + if (!walker.invokeEnterBeforeObjectAndArray() && !stringToInsert.equals(colonWs + comma)) { + formatInsertedString(context, stringToInsert.length()); + } + + if (hasValues) { + AutoPopupController.getInstance(context.getProject()).autoPopupMemberLookup(context.getEditor(), null); + } + } + + public static void formatInsertedString(@NotNull InsertionContext context, + int offset) { + Project project = context.getProject(); + PsiDocumentManager.getInstance(project).commitDocument(context.getDocument()); + CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(project); + codeStyleManager.reformatText(context.getFile(), context.getStartOffset(), context.getTailOffset() + offset); + } +}
\ No newline at end of file |