List<IntentionAction> (Environment env, Editor editor, PsiElement currentElement, boolean hasErrors, List<IntentionAction> intentions) { if (currentElement == null) return intentions; int offset = editor.getCaretModel().getOffset(); /* * When start offset of the element exactly equals to offset in the editor, we have a dubious situation * which we'd like to avoid: sometimes intention looks what's on the left of caret, but we add a parenthesis there and things changed. * E.g. "a" + <caret>"b" allows to join plus, but "a" + (<caret>"b") does not, and this looks legit as the intention reacts on plus, * not on literal. */ List<PsiElement> elementsToWrap = ContainerUtil.filter(myPolicy.getElementsToWrap(currentElement), e -> e.getTextRange().getStartOffset() != offset); if (elementsToWrap.isEmpty()) return intentions; Project project = getProject(); Map<String, IntentionAction> names = intentions.stream().collect(Collectors.toMap(IntentionAction::getText, Function.identity(), (a, b) -> a)); PsiElement elementToWrap = env.generateValue(Generator.sampledFrom(elementsToWrap).noShrink(), null); String text = elementToWrap.getText(); String prefix = myPolicy.getWrapPrefix(); String suffix = myPolicy.getWrapSuffix(); env.logMessage("Wrap '" + StringUtil.shortenTextWithEllipsis(text.replaceAll("\\s+", " "), 50, 10) + "' with '" + prefix + "..." + suffix + "' and rerun daemon"); TextRange range = elementToWrap.getTextRange(); PsiFile file = currentElement.getContainingFile(); WriteCommandAction.runWriteCommandAction(project, () -> { getDocument().insertString(range.getEndOffset(), suffix); getDocument().insertString(range.getStartOffset(), prefix); editor.getCaretModel().moveToOffset(offset + prefix.length()); }); List<String> messages = new ArrayList<>(); boolean newContainsErrorElements = MadTestingUtil.containsErrorElements(getFile().getViewProvider()); if (newContainsErrorElements) { messages.add("File contains parse errors after wrapping"); } else { boolean newHasErrors = !highlightErrors(project, editor).isEmpty(); if (newHasErrors != hasErrors) { messages.add(newHasErrors ? "File contains errors after wrapping" : "File errors were fixed after wrapping"); } } intentions = getAvailableIntentions(editor, file); Map<String, IntentionAction> namesWithParentheses = intentions.stream().collect(Collectors.toMap(IntentionAction::getText, Function.identity(), (a, b) -> a)); Map<String, IntentionAction> added = new HashMap<>(namesWithParentheses); added.keySet().removeAll(names.keySet()); Map<String, IntentionAction> removed = new HashMap<>(names); removed.keySet().removeAll(namesWithParentheses.keySet()); Function<String, String> cleaner = name -> name.replace(prefix, "").replace(suffix, ""); // Exclude pairs like "Extract if (!foo)" and "Extract if (!(foo))" for (Iterator<String> iterator = added.keySet().iterator(); iterator.hasNext(); ) { String newName = iterator.next(); String stripped = cleaner.apply(newName); if (removed.keySet().removeIf(n -> cleaner.apply(n).equals(stripped))) { iterator.remove(); } } if (!added.isEmpty()) { messages.add("Intentions added after parenthesizing:\n" + describeIntentions(added)); } if (!removed.isEmpty()) { messages.add("Intentions removed after parenthesizing:\n" + describeIntentions(removed)); } if (!messages.isEmpty()) { WriteCommandAction.runWriteCommandAction(project, () -> { getDocument().deleteString(range.getStartOffset(), range.getStartOffset() + prefix.length()); getDocument().deleteString(range.getEndOffset(), range.getEndOffset() + suffix.length()); editor.getCaretModel().moveToOffset(offset); }); intentions = getAvailableIntentions(editor, file); Map<String, IntentionAction> namesBackAgain = intentions.stream().collect(Collectors.toMap(IntentionAction::getText, Function.identity(), (a, b) -> a)); if (!namesBackAgain.keySet().equals(names.keySet())) { if (namesBackAgain.keySet().equals(namesWithParentheses.keySet())) { messages.add(0, "Unstable result: intentions changed after parenthesizing, but remain the same when parentheses removed"); } else { messages .add(0, "Unstable result: intentions changed after parenthesizing, but restored in a different way when parentheses removed"); } } LOG.debug("Error occurred, file text before adding parentheses:\n" + file.getText()); throw new AssertionError(String.join("\n", messages)); } return intentions; }