String (@NotNull Editor editor, @Nullable PsiFile file, @NotNull Project project) { PsiDocumentManager.getInstance(project).commitAllDocuments(); TextRange selection = new TextRange(editor.getSelectionModel().getSelectionStart(), editor.getSelectionModel().getSelectionEnd()); PsiElement[] psiElements = PsiTreeUtil.collectElements(file, element -> selection.contains(element.getTextRange())); Document document = EditorFactory.getInstance().createDocument(selection.substring(editor.getDocument().getText())); boolean isXml = file != null && file.getLanguage().is(StdLanguages.XML); int offsetDelta = editor.getSelectionModel().getSelectionStart(); WriteCommandAction.writeCommandAction(project).withName(null).run(() -> { Map<RangeMarker, String> rangeToText = new HashMap<>(); for (PsiElement element : psiElements) { for (PsiReference reference : element.getReferences()) { if (!(reference instanceof PsiQualifiedReference) || ((PsiQualifiedReference)reference).getQualifier() == null) { String canonicalText = reference.getCanonicalText(); TextRange referenceRange = reference.getRangeInElement(); if (referenceRange.isEmpty()) continue; final TextRange elementTextRange = element.getTextRange(); LOG.assertTrue(elementTextRange != null, elementTextRange); final TextRange range = elementTextRange.cutOut(referenceRange).shiftRight(-offsetDelta); final String oldText = document.getText(range); // workaround for Java references: canonicalText contains generics, and we need to cut them off because otherwise // they will be duplicated int pos = canonicalText.indexOf('<'); if (pos > 0 && !oldText.contains("<")) { canonicalText = canonicalText.substring(0, pos); } if (isXml) { //strip namespace prefixes pos = canonicalText.lastIndexOf(':'); if (pos >= 0 && pos < canonicalText.length() - 1 && !oldText.contains(":")) { canonicalText = canonicalText.substring(pos + 1); } } if (!canonicalText.equals(oldText)) { rangeToText.put(document.createRangeMarker(range), canonicalText); } } } } List<RangeMarker> markers = new ArrayList<>(); for (RangeMarker m1 : rangeToText.keySet()) { boolean nested = false; for (RangeMarker m2 : rangeToText.keySet()) { if (m1 != m2 && m2.getStartOffset() <= m1.getStartOffset() && m1.getEndOffset() <= m2.getEndOffset()) { nested = true; break; } } if (!nested) { markers.add(m1); } } for (RangeMarker marker : markers) { final String value = rangeToText.get(marker); document.replaceString(marker.getStartOffset(), marker.getEndOffset(), value); } }); return document.getText().trim(); }