mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-19 21:11:28 +07:00
[util] IDEA-247475 API for HTML concatenations
GitOrigin-RevId: 634bc8da11238b0ce7b89de66af837efe5ccef0f
This commit is contained in:
committed by
intellij-monorepo-bot
parent
f842ce8802
commit
bd2f9d5c3c
@@ -34,6 +34,7 @@ import com.intellij.openapi.util.NlsContexts;
|
||||
import com.intellij.openapi.util.Ref;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.util.registry.Registry;
|
||||
import com.intellij.openapi.util.text.HtmlChunk;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vfs.LocalFileSystem;
|
||||
import com.intellij.openapi.vfs.VirtualFileManager;
|
||||
@@ -519,7 +520,8 @@ public final class CompileDriver {
|
||||
ToolWindowManager.getInstance(myProject).notifyByBalloon(toolWindowId, messageType, statusMessage);
|
||||
}
|
||||
|
||||
final String wrappedMessage = _status != ExitStatus.UP_TO_DATE? "<a href='#'>" + statusMessage + "</a>" : statusMessage;
|
||||
final String wrappedMessage = _status != ExitStatus.UP_TO_DATE?
|
||||
HtmlChunk.link("#", statusMessage).toString() : statusMessage;
|
||||
final Notification notification = CompilerManager.NOTIFICATION_GROUP.createNotification(
|
||||
"", wrappedMessage,
|
||||
messageType.toNotificationType(),
|
||||
|
||||
@@ -19,6 +19,8 @@ import com.intellij.openapi.roots.ui.configuration.projectRoot.LibraryConfigurab
|
||||
import com.intellij.openapi.roots.ui.configuration.projectRoot.StructureConfigurableContext;
|
||||
import com.intellij.openapi.ui.NamedConfigurable;
|
||||
import com.intellij.openapi.util.ActionCallback;
|
||||
import com.intellij.openapi.util.text.HtmlBuilder;
|
||||
import com.intellij.openapi.util.text.HtmlChunk;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.util.PathUtil;
|
||||
import com.intellij.xml.util.XmlStringUtil;
|
||||
@@ -76,16 +78,16 @@ public class LibraryProjectStructureElement extends ProjectStructureElement {
|
||||
}
|
||||
|
||||
private static String createInvalidRootsDescription(List<String> invalidClasses, String rootName, String libraryName) {
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
HtmlBuilder buffer = new HtmlBuilder();
|
||||
final String name = StringUtil.escapeXmlEntities(libraryName);
|
||||
buffer.append("Library ");
|
||||
buffer.append("<a href='http://library/").append(name).append("'>").append(name).append("</a>");
|
||||
buffer.appendLink("http://library/"+name, name);
|
||||
buffer.append(" has broken " + rootName + " " + StringUtil.pluralize("path", invalidClasses.size()) + ":");
|
||||
for (String url : invalidClasses) {
|
||||
buffer.append("<br> ");
|
||||
buffer.br().nbsp(2);
|
||||
buffer.append(PathUtil.toPresentableUrl(url));
|
||||
}
|
||||
return XmlStringUtil.wrapInHtml(buffer);
|
||||
return buffer.wrapWith("html").toString();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -135,8 +137,7 @@ public class LibraryProjectStructureElement extends ProjectStructureElement {
|
||||
@Override
|
||||
public ProjectStructureProblemDescription createUnusedElementWarning() {
|
||||
final List<ConfigurationErrorQuickFix> fixes = Arrays.asList(new AddLibraryToDependenciesFix(), new RemoveLibraryFix(), new RemoveAllUnusedLibrariesFix());
|
||||
final String name = StringUtil.escapeXmlEntities(myLibrary.getName());
|
||||
String libraryName = "<a href='http://library/" + name + "'>" + name + "</a>";
|
||||
String libraryName = HtmlChunk.link("http://library/" + myLibrary.getName(), myLibrary.getName()).toString();
|
||||
return new ProjectStructureProblemDescription(XmlStringUtil.wrapInHtml("Library " + libraryName + " is not used"), null, createPlace(),
|
||||
ProjectStructureProblemType.unused("unused-library"), ProjectStructureProblemDescription.ProblemLevel.PROJECT,
|
||||
fixes, false);
|
||||
|
||||
@@ -26,4 +26,4 @@
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div><div class='bottom'><icon src='AllIcons.Nodes.PpLibFolder'> myLib</div><div class='bottom'><a href='placeholder'>`genericMethod(Class<?>)` on localhost<icon src='AllIcons.Ide.External_link_arrow'></a>
|
||||
</div><div class='bottom'><icon src='AllIcons.Nodes.PpLibFolder'> myLib</div><div class='bottom'><a href="placeholder">`genericMethod(Class<?>)` on localhost<icon src='AllIcons.Ide.External_link_arrow'></a>
|
||||
@@ -19,4 +19,4 @@
|
||||
<div class="block"><a href="psi_element://com.jetbrains.LinkBetweenMethods#m2()"><code>m2()</code></a></div>
|
||||
</li>
|
||||
</ul>
|
||||
</div><div class='bottom'><icon src='AllIcons.Nodes.PpLibFolder'> myLib</div><div class='bottom'><a href='placeholder'>`m1()` on localhost<icon src='AllIcons.Ide.External_link_arrow'></a>
|
||||
</div><div class='bottom'><icon src='AllIcons.Nodes.PpLibFolder'> myLib</div><div class='bottom'><a href="placeholder">`m1()` on localhost<icon src='AllIcons.Ide.External_link_arrow'></a>
|
||||
@@ -24,4 +24,4 @@
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div><div class='bottom'><icon src='AllIcons.Nodes.PpLibFolder'> myLib</div><div class='bottom'><a href='placeholder'>`SimpleInterface` on localhost<icon src='AllIcons.Ide.External_link_arrow'></a>
|
||||
</div><div class='bottom'><icon src='AllIcons.Nodes.PpLibFolder'> myLib</div><div class='bottom'><a href="placeholder">`SimpleInterface` on localhost<icon src='AllIcons.Ide.External_link_arrow'></a>
|
||||
@@ -32,4 +32,4 @@ extends java.lang.Object</pre>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div><div class='bottom'><icon src='AllIcons.Nodes.PpLibFolder'> myLib</div><div class='bottom'><a href='placeholder'>`ClassWithRefLink` on localhost<icon src='AllIcons.Ide.External_link_arrow'></a>
|
||||
</div><div class='bottom'><icon src='AllIcons.Nodes.PpLibFolder'> myLib</div><div class='bottom'><a href="placeholder">`ClassWithRefLink` on localhost<icon src='AllIcons.Ide.External_link_arrow'></a>
|
||||
@@ -26,4 +26,4 @@
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div><div class='bottom'><icon src='AllIcons.Nodes.PpLibFolder'> myLib</div><div class='bottom'><a href='placeholder'>`param()` on localhost<icon src='AllIcons.Ide.External_link_arrow'></a>
|
||||
</div><div class='bottom'><icon src='AllIcons.Nodes.PpLibFolder'> myLib</div><div class='bottom'><a href="placeholder">`param()` on localhost<icon src='AllIcons.Ide.External_link_arrow'></a>
|
||||
@@ -2,6 +2,7 @@
|
||||
package com.intellij.util;
|
||||
|
||||
import com.intellij.openapi.application.PathManager;
|
||||
import com.intellij.openapi.util.NlsSafe;
|
||||
import com.intellij.openapi.util.SystemInfo;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.util.io.FileUtilRt;
|
||||
@@ -43,7 +44,7 @@ public final class PathUtil {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static String toPresentableUrl(@NotNull String url) {
|
||||
public static @NlsSafe String toPresentableUrl(@NotNull String url) {
|
||||
return getLocalPath(VirtualFileManager.extractPath(url));
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import com.intellij.openapi.util.Condition;
|
||||
import com.intellij.openapi.util.Disposer;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.registry.Registry;
|
||||
import com.intellij.openapi.util.text.HtmlChunk;
|
||||
import com.intellij.openapi.vcs.AbstractVcs;
|
||||
import com.intellij.ui.CheckedTreeNode;
|
||||
import com.intellij.util.Function;
|
||||
@@ -581,7 +582,7 @@ public final class PushController implements Disposable {
|
||||
for (int i = 0; i < commits.size(); ++i) {
|
||||
if (i >= commitsNum) {
|
||||
@NonNls
|
||||
final VcsLinkedTextComponent moreCommitsLink = new VcsLinkedTextComponent("<a href='loadMore'>...</a>", new VcsLinkListener() {
|
||||
final VcsLinkedTextComponent moreCommitsLink = new VcsLinkedTextComponent(HtmlChunk.link("loadMore", "...").toString(), new VcsLinkListener() {
|
||||
@Override
|
||||
public void hyperlinkActivated(@NotNull DefaultMutableTreeNode sourceNode, @NotNull MouseEvent event) {
|
||||
TreeNode parent = sourceNode.getParent();
|
||||
|
||||
@@ -35,6 +35,8 @@ import com.intellij.openapi.editor.ex.EditorSettingsExternalizable;
|
||||
import com.intellij.openapi.editor.ex.util.EditorUtil;
|
||||
import com.intellij.openapi.keymap.KeymapUtil;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.text.HtmlBuilder;
|
||||
import com.intellij.openapi.util.text.HtmlChunk;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.PsiFile;
|
||||
@@ -254,16 +256,16 @@ public class FileInEditorProcessor {
|
||||
@Override
|
||||
@NotNull
|
||||
public String getMessage() {
|
||||
StringBuilder builder = new StringBuilder("<html>");
|
||||
HtmlBuilder builder = new HtmlBuilder();
|
||||
LayoutCodeInfoCollector notifications = myProcessor.getInfoCollector();
|
||||
LOG.assertTrue(notifications != null);
|
||||
|
||||
if (notifications.isEmpty() && !myNoChangesDetected) {
|
||||
if (myProcessChangesTextOnly) {
|
||||
builder.append("No lines changed: changes since last revision are already properly formatted").append("<br>");
|
||||
builder.append("No lines changed: changes since last revision are already properly formatted").br();
|
||||
}
|
||||
else {
|
||||
builder.append("No lines changed: content is already properly formatted").append("<br>");
|
||||
builder.append("No lines changed: content is already properly formatted").br();
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -277,26 +279,25 @@ public class FileInEditorProcessor {
|
||||
builder.append(" in changes since last revision");
|
||||
}
|
||||
|
||||
builder.append("<br>");
|
||||
builder.br();
|
||||
}
|
||||
else if (myNoChangesDetected) {
|
||||
builder.append("No lines changed: no changes since last revision").append("<br>");
|
||||
builder.append("No lines changed: no changes since last revision").br();
|
||||
}
|
||||
|
||||
String optimizeImportsNotification = notifications.getOptimizeImportsNotification();
|
||||
if (optimizeImportsNotification != null) {
|
||||
builder.append(StringUtil.capitalize(optimizeImportsNotification)).append("<br>");
|
||||
builder.append(StringUtil.capitalize(optimizeImportsNotification)).br();
|
||||
}
|
||||
}
|
||||
|
||||
String shortcutText = KeymapUtil.getFirstKeyboardShortcutText(ActionManager.getInstance().getAction("ShowReformatFileDialog"));
|
||||
String color = ColorUtil.toHex(JBColor.gray);
|
||||
String color = ColorUtil.toHtmlColor(JBColor.gray);
|
||||
|
||||
builder.append("<span style='color:#").append(color).append("'>")
|
||||
.append("<a href=''>Show</a> reformat dialog: ").append(shortcutText).append("</span>")
|
||||
.append("</html>");
|
||||
builder.append(HtmlChunk.span("color:"+color)
|
||||
.child(HtmlChunk.raw("<a href=''>Show</a> reformat dialog: ")).addText(shortcutText));
|
||||
|
||||
return builder.toString();
|
||||
return builder.wrapWith("html").toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -44,6 +44,7 @@ import com.intellij.openapi.util.DimensionService;
|
||||
import com.intellij.openapi.util.Disposer;
|
||||
import com.intellij.openapi.util.InvalidDataException;
|
||||
import com.intellij.openapi.util.registry.Registry;
|
||||
import com.intellij.openapi.util.text.HtmlChunk;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.openapi.wm.IdeFocusManager;
|
||||
@@ -968,9 +969,6 @@ public class DocumentationComponent extends JPanel implements Disposable, DataPr
|
||||
}
|
||||
|
||||
String title = manager.getTitle(element);
|
||||
if (title != null) {
|
||||
title = StringUtil.escapeXmlEntities(title);
|
||||
}
|
||||
if (externalUrl == null) {
|
||||
List<String> urls = provider.getUrlFor(element, originalElement);
|
||||
if (urls != null) {
|
||||
@@ -997,27 +995,25 @@ public class DocumentationComponent extends JPanel implements Disposable, DataPr
|
||||
if (link != null) return link;
|
||||
}
|
||||
|
||||
return "<a href='external_doc'>External documentation" +
|
||||
(title == null ? "" : (" for `" + title + "`")) +
|
||||
"<icon src='AllIcons.Ide.External_link_arrow'></a></div>";
|
||||
String linkText = "External documentation" + (title == null ? "" : " for `" + title + "`");
|
||||
return HtmlChunk.link("external_doc", linkText)
|
||||
.child(HtmlChunk.tag("icon").attr("src", "AllIcons.Ide.External_link_arrow")).toString();
|
||||
}
|
||||
|
||||
private static String getLink(String title, String url) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
String hostname = getHostname(url);
|
||||
if (hostname == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
result.append("<a href='").append(url).append("'>");
|
||||
String linkText;
|
||||
if (title == null) {
|
||||
result.append("Documentation");
|
||||
linkText = "Documentation on " + hostname;
|
||||
}
|
||||
else {
|
||||
result.append("`").append(title).append("`");
|
||||
linkText = "`" + title + "` on " + hostname;
|
||||
}
|
||||
result.append(" on ").append(hostname).append("</a>");
|
||||
return result.toString();
|
||||
return HtmlChunk.link(url, linkText).toString();
|
||||
}
|
||||
|
||||
static boolean shouldShowExternalDocumentationLink(DocumentationProvider provider,
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.intellij.codeInsight.highlighting.ReadWriteAccessDetector;
|
||||
import com.intellij.openapi.fileEditor.FileEditorLocation;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.DialogWrapper;
|
||||
import com.intellij.openapi.util.text.HtmlBuilder;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.refactoring.RefactoringBundle;
|
||||
@@ -109,14 +110,14 @@ public class ConflictsDialog extends DialogWrapper{
|
||||
|
||||
panel.add(new JLabel(RefactoringBundle.message("the.following.problems.were.found")), BorderLayout.NORTH);
|
||||
|
||||
@NonNls StringBuilder buf = new StringBuilder();
|
||||
HtmlBuilder buf = new HtmlBuilder();
|
||||
|
||||
for (int i = 0; i < Math.min(myConflictDescriptions.length, MAX_CONFLICTS_SHOWN); i++) {
|
||||
buf.append(myConflictDescriptions[i]).append("<br><br>");
|
||||
buf.append(myConflictDescriptions[i]).br().br();
|
||||
}
|
||||
|
||||
if (myConflictDescriptions.length > MAX_CONFLICTS_SHOWN) {
|
||||
buf.append("<a href='" + EXPAND_LINK + "'>Show more...</a>");
|
||||
buf.appendLink(EXPAND_LINK, "Show more...");
|
||||
}
|
||||
|
||||
JEditorPane messagePane = new JEditorPane();
|
||||
|
||||
@@ -3,11 +3,7 @@ package com.intellij.openapi.keymap.impl.ui;
|
||||
|
||||
import com.intellij.diagnostic.VMOptions;
|
||||
import com.intellij.icons.AllIcons;
|
||||
import com.intellij.ide.CommonActionsManager;
|
||||
import com.intellij.ide.DataManager;
|
||||
import com.intellij.ide.DefaultTreeExpander;
|
||||
import com.intellij.ide.IdeBundle;
|
||||
import com.intellij.ide.TreeExpander;
|
||||
import com.intellij.ide.*;
|
||||
import com.intellij.openapi.Disposable;
|
||||
import com.intellij.openapi.actionSystem.*;
|
||||
import com.intellij.openapi.actionSystem.ex.QuickList;
|
||||
@@ -29,6 +25,7 @@ import com.intellij.openapi.ui.popup.JBPopupFactory;
|
||||
import com.intellij.openapi.ui.popup.ListPopup;
|
||||
import com.intellij.openapi.util.Disposer;
|
||||
import com.intellij.openapi.util.registry.Registry;
|
||||
import com.intellij.openapi.util.text.HtmlBuilder;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.wm.IdeFocusManager;
|
||||
import com.intellij.openapi.wm.IdeFrame;
|
||||
@@ -145,7 +142,7 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi
|
||||
if (allConflicts.isEmpty())
|
||||
return;
|
||||
|
||||
String htmlBody = "";
|
||||
HtmlBuilder htmlBody = new HtmlBuilder();
|
||||
final Map<String, Runnable> href2linkAction = new HashMap<>();
|
||||
int count = 0;
|
||||
boolean empty = true;
|
||||
@@ -159,8 +156,8 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi
|
||||
final AnAction act = ActionManager.getInstance().getAction(actId);
|
||||
final String actText = act == null ? actId : act.getTemplateText();
|
||||
if (!empty)
|
||||
htmlBody += ", ";
|
||||
htmlBody += "<a href='" + actId + "'>" + actText + "</a>";
|
||||
htmlBody.append(", ");
|
||||
htmlBody.appendLink(actId, actText);
|
||||
|
||||
empty = false;
|
||||
++count;
|
||||
@@ -169,10 +166,10 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi
|
||||
}
|
||||
|
||||
if (count > 2 && allConflicts.size() > count) {
|
||||
final @NotNull String text = String.format("%d more", allConflicts.size() - count);
|
||||
htmlBody += " and <a href='" + text + "'>" + text + "</a>";
|
||||
String actionId = "show.more";
|
||||
htmlBody.append(" and ").appendLink(actionId, String.format("%d more", allConflicts.size() - count));
|
||||
|
||||
href2linkAction.put(text, ()->{
|
||||
href2linkAction.put(actionId, ()->{
|
||||
myShowOnlyConflicts = true;
|
||||
myActionsTree.setBaseFilter(systemShortcuts.createKeymapConflictsActionFilter());
|
||||
myActionsTree.filter(null, myQuickLists);
|
||||
@@ -180,9 +177,10 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi
|
||||
});
|
||||
}
|
||||
|
||||
htmlBody += " shortcuts conflict with the macOS system shortcuts.<br>Assign custom shortcuts or change the macOS system settings.</p></html>";
|
||||
htmlBody.append(" shortcuts conflict with the macOS system shortcuts.")
|
||||
.br().append("Assign custom shortcuts or change the macOS system settings.");
|
||||
|
||||
JBLabel jbLabel = new JBLabel(createWarningHtmlText(htmlBody)) {
|
||||
JBLabel jbLabel = new JBLabel(createWarningHtmlText(htmlBody.toString())) {
|
||||
@NotNull
|
||||
@Override
|
||||
protected HyperlinkListener createHyperlinkListener() {
|
||||
|
||||
@@ -31,9 +31,12 @@ import com.intellij.openapi.util.AtomicNotNullLazyValue;
|
||||
import com.intellij.openapi.util.BuildNumber;
|
||||
import com.intellij.openapi.util.NotNullLazyValue;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.util.text.HtmlBuilder;
|
||||
import com.intellij.openapi.util.text.HtmlChunk;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.util.LineSeparator;
|
||||
import com.intellij.util.concurrency.AppExecutorUtil;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.text.DateFormatUtil;
|
||||
import org.jdom.JDOMException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -289,9 +292,10 @@ final class UpdateCheckerComponent {
|
||||
}
|
||||
|
||||
String title = IdeBundle.message("update.installed.notification.title");
|
||||
String message = "<html>" + StringUtil.join(descriptors, descriptor -> {
|
||||
return "<a href='" + descriptor.getPluginId().getIdString() + "'>" + descriptor.getName() + "</a>";
|
||||
}, ", ") + "</html>";
|
||||
String message = new HtmlBuilder()
|
||||
.appendWithSeparators(HtmlChunk.text(", "), ContainerUtil.map(
|
||||
descriptors, descriptor -> HtmlChunk.link(descriptor.getPluginId().getIdString(), descriptor.getName())))
|
||||
.wrapWith("html").toString();
|
||||
|
||||
UpdateChecker.getNotificationGroup().createNotification(title, message, NotificationType.INFORMATION, (notification, event) -> {
|
||||
String id = event.getDescription();
|
||||
|
||||
@@ -26,6 +26,8 @@ import com.intellij.openapi.ui.popup.Balloon;
|
||||
import com.intellij.openapi.util.Disposer;
|
||||
import com.intellij.openapi.util.Factory;
|
||||
import com.intellij.openapi.util.Segment;
|
||||
import com.intellij.openapi.util.text.HtmlBuilder;
|
||||
import com.intellij.openapi.util.text.HtmlChunk;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.openapi.wm.ToolWindowId;
|
||||
@@ -97,16 +99,17 @@ final class SearchForUsagesRunnable implements Runnable {
|
||||
@NotNull
|
||||
private static String createOptionsHtml(@NonNls UsageTarget @NotNull [] searchFor) {
|
||||
KeyboardShortcut shortcut = UsageViewImpl.getShowUsagesWithSettingsShortcut(searchFor);
|
||||
String shortcutText = "";
|
||||
HtmlBuilder builder = new HtmlBuilder()
|
||||
.appendLink(FIND_OPTIONS_HREF_TARGET, "Find Options...");
|
||||
if (shortcut != null) {
|
||||
shortcutText = " (" + KeymapUtil.getShortcutText(shortcut) + ")";
|
||||
builder.nbsp(1).append("(" + KeymapUtil.getShortcutText(shortcut) + ")");
|
||||
}
|
||||
return "<a href='" + FIND_OPTIONS_HREF_TARGET + "'>Find Options...</a>" + shortcutText;
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String createSearchInProjectHtml() {
|
||||
return "<a href='" + SEARCH_IN_PROJECT_HREF_TARGET + "'>Search in Project</a>";
|
||||
return HtmlChunk.link(SEARCH_IN_PROJECT_HREF_TARGET, "Search in Project").toString();
|
||||
}
|
||||
|
||||
private void notifyByFindBalloon(@Nullable final HyperlinkListener listener,
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
// Copyright 2000-2020 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.intellij.openapi.util.text;
|
||||
|
||||
import com.intellij.openapi.util.NlsSafe;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A simple builder to create HTML fragments. It encapsulates a series of {@link HtmlChunk} objects.
|
||||
*/
|
||||
public final class HtmlBuilder {
|
||||
private final List<HtmlChunk> myChunks = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Appends a new chunk to this builder
|
||||
*
|
||||
* @param chunk chunk to append
|
||||
* @return this builder
|
||||
*/
|
||||
@Contract("_ -> this")
|
||||
public HtmlBuilder append(@NotNull HtmlChunk chunk) {
|
||||
myChunks.add(chunk);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a text chunk to this builder
|
||||
*
|
||||
* @param text text to append (must not be escaped by caller)
|
||||
* @return this builder
|
||||
*/
|
||||
@Contract("_ -> this")
|
||||
public HtmlBuilder append(@NotNull @Nls String text) {
|
||||
myChunks.add(HtmlChunk.text(text));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a raw html text to this builder. Should be sued with care.
|
||||
* The purpose of this method is to be able to externalize the text with embedded link. E.g.:
|
||||
* {@code "Click <a href=\"...\">here</a> for details"}.
|
||||
*
|
||||
* @param rawHtml raw HTML content. It's the responsibility of the caller to balance tags and escape HTML entities.
|
||||
* @return this builder
|
||||
*/
|
||||
@Contract("_ -> this")
|
||||
public HtmlBuilder appendRaw(@NotNull @Nls String rawHtml) {
|
||||
myChunks.add(HtmlChunk.raw(rawHtml));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a link element to this builder
|
||||
*
|
||||
* @param target link target (href)
|
||||
* @param text link text
|
||||
* @return this builder
|
||||
*/
|
||||
@Contract("_, _ -> this")
|
||||
public HtmlBuilder appendLink(@NotNull @NonNls String target, @NotNull @Nls String text) {
|
||||
myChunks.add(HtmlChunk.link(target, text));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a collection of chunks interleaving them with a supplied separator chunk
|
||||
*
|
||||
* @param separator a separator chunk
|
||||
* @param children chunks to append
|
||||
* @return this builder
|
||||
*/
|
||||
@Contract("_, _ -> this")
|
||||
public HtmlBuilder appendWithSeparators(@NotNull HtmlChunk separator, @NotNull Iterable<HtmlChunk> children) {
|
||||
boolean first = true;
|
||||
for (HtmlChunk child : children) {
|
||||
if (!first) {
|
||||
append(separator);
|
||||
}
|
||||
first = false;
|
||||
append(child);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a series of non-breaking spaces ({@code } entities).
|
||||
*
|
||||
* @param count number of non-breaking spaces to append
|
||||
* @return this builder
|
||||
*/
|
||||
@Contract("_ -> this")
|
||||
public HtmlBuilder nbsp(int count) {
|
||||
myChunks.add(HtmlChunk.nbsp(count));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a line-break ({@code <br/>}).
|
||||
*
|
||||
* @return this builder
|
||||
*/
|
||||
@Contract(" -> this")
|
||||
public HtmlBuilder br() {
|
||||
myChunks.add(HtmlChunk.br());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps this builder content with a specified tag
|
||||
*
|
||||
* @param tag name of the tag to wrap with
|
||||
* @return a new Element object that contains elements from this builder
|
||||
*/
|
||||
public HtmlChunk.Element wrapWith(@NotNull @NonNls String tag) {
|
||||
return HtmlChunk.tag(tag).children(myChunks.toArray(new HtmlChunk[0]));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if no elements were added to this builder
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return myChunks.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a rendered HTML representation of all the chunks in this builder.
|
||||
*/
|
||||
@Override
|
||||
public @NlsSafe String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (HtmlChunk chunk : myChunks) {
|
||||
chunk.appendTo(sb);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
291
platform/util/src/com/intellij/openapi/util/text/HtmlChunk.java
Normal file
291
platform/util/src/com/intellij/openapi/util/text/HtmlChunk.java
Normal file
@@ -0,0 +1,291 @@
|
||||
// Copyright 2000-2020 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.intellij.openapi.util.text;
|
||||
|
||||
import com.intellij.openapi.util.NlsSafe;
|
||||
import com.intellij.util.containers.UnmodifiableHashMap;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* An immutable representation of HTML node. Could be used as a DSL to quickly generate HTML strings.
|
||||
*
|
||||
* @see HtmlBuilder
|
||||
*/
|
||||
public abstract class HtmlChunk {
|
||||
private static class Text extends HtmlChunk {
|
||||
private final String myContent;
|
||||
|
||||
private Text(String content) {
|
||||
myContent = content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendTo(@NotNull StringBuilder builder) {
|
||||
builder.append(StringUtil.escapeXmlEntities(myContent));
|
||||
}
|
||||
}
|
||||
|
||||
private static class Raw extends HtmlChunk {
|
||||
private final String myContent;
|
||||
|
||||
private Raw(String content) {
|
||||
myContent = content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendTo(@NotNull StringBuilder builder) {
|
||||
builder.append(myContent);
|
||||
}
|
||||
}
|
||||
|
||||
private static class Nbsp extends HtmlChunk {
|
||||
private final int myCount;
|
||||
|
||||
private Nbsp(int count) {
|
||||
myCount = count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendTo(@NotNull StringBuilder builder) {
|
||||
builder.append(StringUtil.repeat(" ", myCount));
|
||||
}
|
||||
}
|
||||
|
||||
public static class Element extends HtmlChunk {
|
||||
private static final Element BODY = tag("body");
|
||||
private static final Element HTML = tag("html");
|
||||
private static final Element BR = tag("br");
|
||||
private static final Element DIV = tag("div");
|
||||
private static final Element SPAN = tag("span");
|
||||
|
||||
private final String myTagName;
|
||||
private final UnmodifiableHashMap<String, String> myAttributes;
|
||||
private final List<HtmlChunk> myChildren;
|
||||
|
||||
private Element(String name,
|
||||
UnmodifiableHashMap<String, String> attributes,
|
||||
List<HtmlChunk> children) {
|
||||
myTagName = name;
|
||||
myAttributes = attributes;
|
||||
myChildren = children;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendTo(@NotNull StringBuilder builder) {
|
||||
builder.append('<').append(myTagName);
|
||||
myAttributes.forEach((attrKey, attrValue) -> {
|
||||
builder.append(' ').append(attrKey).append("=\"").append(StringUtil.escapeXmlEntities(attrValue)).append('"');
|
||||
});
|
||||
if (myChildren.isEmpty()) {
|
||||
builder.append("/>");
|
||||
} else {
|
||||
builder.append(">");
|
||||
for (HtmlChunk child : myChildren) {
|
||||
child.appendTo(builder);
|
||||
}
|
||||
builder.append("</").append(myTagName).append(">");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param name attribute name
|
||||
* @param value attribute value
|
||||
* @return a new element that is like this element but has the specified attribute added or replaced
|
||||
*/
|
||||
public Element attr(@NonNls String name, String value) {
|
||||
return new Element(myTagName, myAttributes.with(name, value), myChildren);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param style CSS style specification
|
||||
* @return a new element that is like this element but has the specified style added or replaced
|
||||
*/
|
||||
public Element style(@NonNls String style) {
|
||||
return attr("style", style);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param text text to add to the list of children (should not be escaped)
|
||||
* @return a new element that is like this element but has an extra text child
|
||||
*/
|
||||
public Element addText(@NotNull @Nls String text) {
|
||||
return child(text(text));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param chunks chunks to add to the list of children
|
||||
* @return a new element that is like this element but has extra children
|
||||
*/
|
||||
public Element children(@NotNull HtmlChunk @NotNull ... chunks) {
|
||||
if (myChildren.isEmpty()) {
|
||||
return new Element(myTagName, myAttributes, Arrays.asList(chunks));
|
||||
}
|
||||
List<HtmlChunk> newChildren = new ArrayList<>(myChildren.size() + chunks.length);
|
||||
newChildren.addAll(myChildren);
|
||||
Collections.addAll(myChildren, chunks);
|
||||
return new Element(myTagName, myAttributes, newChildren);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param chunk a chunk to add to the list of children
|
||||
* @return a new element that is like this element but has an extra child
|
||||
*/
|
||||
public @NotNull Element child(@NotNull HtmlChunk chunk) {
|
||||
if (myChildren.isEmpty()) {
|
||||
return new Element(myTagName, myAttributes, Collections.singletonList(chunk));
|
||||
}
|
||||
List<HtmlChunk> newChildren = new ArrayList<>(myChildren.size() + 1);
|
||||
newChildren.addAll(myChildren);
|
||||
newChildren.add(chunk);
|
||||
return new Element(myTagName, myAttributes, newChildren);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param tagName name of the tag to wrap with
|
||||
* @return an element that wraps this element
|
||||
*/
|
||||
public @NotNull Element wrapWith(@NotNull @NonNls String tagName) {
|
||||
return new Element(tagName, UnmodifiableHashMap.empty(), Collections.singletonList(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a B element that wraps this element
|
||||
*/
|
||||
public @NotNull Element bold() {
|
||||
return wrapWith("b");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return an I element that wraps this element
|
||||
*/
|
||||
public @NotNull Element italic() {
|
||||
return wrapWith("i");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param tagName name of the tag
|
||||
* @return an empty tag
|
||||
*/
|
||||
public static @NotNull Element tag(@NotNull @NonNls String tagName) {
|
||||
return new Element(tagName, UnmodifiableHashMap.empty(), Collections.emptyList());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a <div> element
|
||||
*/
|
||||
public static @NotNull Element div() {
|
||||
return Element.DIV;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a <div> element with a specified style.
|
||||
*/
|
||||
public static @NotNull Element div(@NotNull @NonNls String style) {
|
||||
return Element.DIV.style(style);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a <span> element.
|
||||
*/
|
||||
public static @NotNull Element span() {
|
||||
return Element.SPAN;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a <span> element with a specified style.
|
||||
*/
|
||||
public static @NotNull Element span(@NonNls @NotNull String style) {
|
||||
return Element.SPAN.style(style);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a <br> element.
|
||||
*/
|
||||
public static @NotNull Element br() {
|
||||
return Element.BR;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a <body> element.
|
||||
*/
|
||||
public static @NotNull Element body() {
|
||||
return Element.BODY;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a <html> element.
|
||||
*/
|
||||
public static @NotNull Element html() {
|
||||
return Element.HTML;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a HTML text node that represents a given number of non-breaking spaces
|
||||
*
|
||||
* @param count number of non-breaking spaces
|
||||
* @return HtmlChunk that represents a sequence of non-breaking spaces
|
||||
*/
|
||||
public static @NotNull HtmlChunk nbsp(int count) {
|
||||
if (count <= 0) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
return new Nbsp(count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a HTML text node
|
||||
*
|
||||
* @param text text to display (no escaping should be done by caller).
|
||||
* @return HtmlChunk that represents a HTML text node.
|
||||
*/
|
||||
public static @NotNull HtmlChunk text(@NotNull @Nls String text) {
|
||||
return new Text(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a chunk that represents a piece of raw HTML. Should be used with care!
|
||||
* The purpose of this method is to be able to externalize the text with embedded link. E.g.:
|
||||
* {@code "Click <a href=\"...\">here</a> for details"}.
|
||||
*
|
||||
* @param rawHtml raw HTML content. It's the responsibility of the caller to balance tags and escape HTML entities.
|
||||
* @return the HtmlChunk that represents the supplied content.
|
||||
*/
|
||||
public static @NotNull HtmlChunk raw(@NotNull @Nls String rawHtml) {
|
||||
return new Raw(rawHtml);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an element that represents a simple HTML link.
|
||||
*
|
||||
* @param target link target (HREF)
|
||||
* @param text link text
|
||||
* @return the Element that represents a link
|
||||
*/
|
||||
public static @NotNull Element link(@NotNull @NonNls String target, @NotNull @Nls String text) {
|
||||
return new Element("a", UnmodifiableHashMap.<String, String>empty().with("href", target), Collections.singletonList(text(text)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the rendered HTML representation of this chunk to the supplied builder
|
||||
*
|
||||
* @param builder builder to append to.
|
||||
*/
|
||||
abstract public void appendTo(@NotNull StringBuilder builder);
|
||||
|
||||
/**
|
||||
* @return the rendered HTML representation of this chunk.
|
||||
*/
|
||||
@Override
|
||||
public @NlsSafe @NotNull String toString() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
appendTo(builder);
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright 2000-2020 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.intellij.util.text;
|
||||
|
||||
import com.intellij.openapi.util.text.HtmlBuilder;
|
||||
import com.intellij.openapi.util.text.HtmlChunk;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class HtmlBuilderTest {
|
||||
@Test
|
||||
public void create() {
|
||||
assertEquals("", new HtmlBuilder().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isEmpty() {
|
||||
assertTrue(new HtmlBuilder().isEmpty());
|
||||
assertFalse(new HtmlBuilder().append("foo").isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void append() {
|
||||
assertEquals("hello world!", new HtmlBuilder().append("hello ").append("world!").toString());
|
||||
assertEquals("<click here>", new HtmlBuilder().append("<click here>").toString());
|
||||
assertEquals("<br/><click here>", new HtmlBuilder().append(HtmlChunk.br()).append("<click here>").toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void appendLink() {
|
||||
assertEquals("<a href=\"url\">click</a> for more info", new HtmlBuilder().appendLink("url", "click").append(" for more info").toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void appendWithSeparators() {
|
||||
assertEquals("", new HtmlBuilder().appendWithSeparators(HtmlChunk.br(), Collections.emptyList()).toString());
|
||||
String[] data = {"foo", "bar", "baz"};
|
||||
String html = new HtmlBuilder().appendWithSeparators(HtmlChunk.br(), ContainerUtil.map(data, d -> HtmlChunk.link(d, d))).toString();
|
||||
assertEquals("<a href=\"foo\">foo</a><br/><a href=\"bar\">bar</a><br/><a href=\"baz\">baz</a>", html);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void wrapWith() {
|
||||
assertEquals("<html>Click <a href=\"foo\">here</a></html>",
|
||||
new HtmlBuilder().append("Click ").appendLink("foo", "here").wrapWith("html").toString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// Copyright 2000-2020 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.intellij.util.text;
|
||||
|
||||
import com.intellij.openapi.util.text.HtmlChunk;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class HtmlChunkTest {
|
||||
@Test
|
||||
public void text() {
|
||||
assertEquals("foo", HtmlChunk.text("foo").toString());
|
||||
assertEquals("<a href="hello">", HtmlChunk.text("<a href=\"hello\">").toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void raw() {
|
||||
assertEquals("foo", HtmlChunk.raw("foo").toString());
|
||||
assertEquals("<a href=\"hello\">", HtmlChunk.raw("<a href=\"hello\">").toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nbsp() {
|
||||
assertEquals(" ", HtmlChunk.nbsp(1).toString());
|
||||
assertEquals(" ", HtmlChunk.nbsp(3).toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void link() {
|
||||
assertEquals("<a href=\"target\"><Click here></a>", HtmlChunk.link("target", "<Click here>").toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tag() {
|
||||
assertEquals("<b/>", HtmlChunk.tag("b").toString());
|
||||
assertEquals("<br/>", HtmlChunk.br().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void div() {
|
||||
assertEquals("<div/>", HtmlChunk.div().toString());
|
||||
assertEquals("<div style=\"color: blue\"/>", HtmlChunk.div("color: blue").toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void attr() {
|
||||
assertEquals("<p align=\"left\"/>", HtmlChunk.tag("p").attr("align", "left").toString());
|
||||
assertEquals("<p align=\"right\"/>", HtmlChunk.tag("p").attr("align", "left").attr("align", "right").toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void children() {
|
||||
assertEquals("<p align=\"left\"><br/></p>", HtmlChunk.tag("p").attr("align", "left").child(HtmlChunk.br()).toString());
|
||||
assertEquals("<p><hello></p>", HtmlChunk.tag("p").child(HtmlChunk.text("<hello>")).toString());
|
||||
assertEquals("<p><hello></p>", HtmlChunk.tag("p").addText("<hello>").toString());
|
||||
assertEquals("<p><a href=\"ref\"><hello></a></p>", HtmlChunk.tag("p").child(HtmlChunk.link("ref", "<hello>")).toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void wrapWith() {
|
||||
assertEquals("<p>hello</p>", HtmlChunk.text("hello").wrapWith("p").toString());
|
||||
assertEquals("<b>hello</b>", HtmlChunk.text("hello").bold().toString());
|
||||
assertEquals("<i>hello</i>", HtmlChunk.text("hello").italic().toString());
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import com.intellij.openapi.application.AccessToken;
|
||||
import com.intellij.openapi.progress.ProgressIndicator;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.text.HtmlBuilder;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vcs.VcsNotifier;
|
||||
import com.intellij.openapi.vcs.changes.Change;
|
||||
@@ -135,20 +136,18 @@ class GitCheckoutOperation extends GitBranchOperation {
|
||||
}
|
||||
else {
|
||||
Collection<GitRepository> successfulRepositories = getSuccessfulRepositories();
|
||||
HtmlBuilder builder = new HtmlBuilder();
|
||||
String mentionSuccess = GitBundle.message("checkout.operation.in", getSuccessMessage(),
|
||||
successfulRepositories.size(),
|
||||
joinShortNames(successfulRepositories, REPOSITORIES_LIMIT));
|
||||
String mentionSkipped = wereSkipped()
|
||||
? UIUtil.BR + revisionNotFound
|
||||
: "";
|
||||
builder.appendRaw(mentionSuccess);
|
||||
if (wereSkipped()) {
|
||||
builder.br().append(revisionNotFound);
|
||||
}
|
||||
builder.br().appendLink(ROLLBACK_HREF_ATTRIBUTE, GitBundle.message("checkout.operation.rollback"));
|
||||
|
||||
VcsNotifier.getInstance(myProject).notifySuccess("",
|
||||
mentionSuccess +
|
||||
mentionSkipped +
|
||||
UIUtil.BR +
|
||||
"<a href='" + ROLLBACK_HREF_ATTRIBUTE + "'>" + //NON-NLS
|
||||
GitBundle.message("checkout.operation.rollback")
|
||||
+ "</a>", //NON-NLS
|
||||
builder.toString(),
|
||||
new RollbackOperationNotificationListener());
|
||||
}
|
||||
notifyBranchHasChanged(myStartPointReference);
|
||||
|
||||
@@ -8,11 +8,11 @@ import com.intellij.openapi.application.AccessToken;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.text.HtmlBuilder;
|
||||
import com.intellij.openapi.vcs.VcsNotifier;
|
||||
import com.intellij.openapi.vcs.changes.Change;
|
||||
import com.intellij.openapi.vcs.changes.ChangeListManager;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import com.intellij.vcs.log.Hash;
|
||||
import git4idea.GitUtil;
|
||||
import git4idea.commands.*;
|
||||
@@ -147,7 +147,7 @@ class GitMergeOperation extends GitBranchOperation {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void notifySuccess(@NotNull String message) {
|
||||
protected void notifySuccess(@NotNull @Nls String message) {
|
||||
switch (myDeleteOnMerge) {
|
||||
case DELETE:
|
||||
super.notifySuccess(message);
|
||||
@@ -155,7 +155,7 @@ class GitMergeOperation extends GitBranchOperation {
|
||||
break;
|
||||
case PROPOSE:
|
||||
String deleteBranch = GitBundle.message("merge.operation.delete.branch", myBranchToMerge);
|
||||
String description = message + UIUtil.BR + "<a href='" + DELETE_HREF_ATTRIBUTE + "'>" + deleteBranch + "</a>"; //NON-NLS
|
||||
String description = new HtmlBuilder().appendRaw(message).br().appendLink(DELETE_HREF_ATTRIBUTE, deleteBranch).toString();
|
||||
VcsNotifier.getInstance(myProject).notifySuccess("", description, new DeleteMergedLocalBranchNotificationListener());
|
||||
break;
|
||||
case NOTHING:
|
||||
@@ -327,11 +327,13 @@ class GitMergeOperation extends GitBranchOperation {
|
||||
@NotNull
|
||||
@Override
|
||||
protected String getRollbackProposal() {
|
||||
return GitBundle.message("merge.operation.however.merge.has.succeeded.for.the.following.repositories", getSuccessfulRepositories().size()) +
|
||||
UIUtil.BR +
|
||||
successfulRepositoriesJoined() +
|
||||
UIUtil.BR +
|
||||
GitBundle.message("merge.operation.you.may.rollback.not.to.let.branches.diverge");
|
||||
return new HtmlBuilder()
|
||||
.append(
|
||||
GitBundle.message("merge.operation.however.merge.has.succeeded.for.the.following.repositories", getSuccessfulRepositories().size()))
|
||||
.br()
|
||||
.appendRaw(successfulRepositoriesJoined())
|
||||
.br()
|
||||
.append(GitBundle.message("merge.operation.you.may.rollback.not.to.let.branches.diverge")).toString();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
||||
@@ -12,6 +12,8 @@ import com.intellij.openapi.ui.Messages;
|
||||
import com.intellij.openapi.util.Couple;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.Ref;
|
||||
import com.intellij.openapi.util.text.HtmlBuilder;
|
||||
import com.intellij.openapi.util.text.HtmlChunk;
|
||||
import com.intellij.openapi.vcs.*;
|
||||
import com.intellij.openapi.vcs.changes.ChangesUtil;
|
||||
import com.intellij.openapi.vcs.changes.CommitContext;
|
||||
@@ -294,22 +296,24 @@ public class GitCheckinHandlerFactory extends VcsCheckinHandlerFactory {
|
||||
|
||||
final String title;
|
||||
final String message;
|
||||
final CharSequence rootPath = detachedRoot.myRoot.getPresentableUrl();
|
||||
final String rootPath = detachedRoot.myRoot.getPresentableUrl();
|
||||
final String messageCommonStart = "The Git repository at the following path";
|
||||
if (detachedRoot.myRebase) {
|
||||
title = "Unfinished Rebase Process";
|
||||
message = messageCommonStart + " has an <b>unfinished rebase</b> process: <br/>" +
|
||||
"<b>" + rootPath + "</b><br>" +
|
||||
"You probably want to <b>continue rebase</b> instead of committing. <br/>" +
|
||||
"Committing during rebase may lead to the commit loss. <br/>" +
|
||||
readMore("https://www.kernel.org/pub/software/scm/git/docs/git-rebase.html", "Read more about Git rebase");
|
||||
message = new HtmlBuilder()
|
||||
.appendRaw(messageCommonStart + " has an <b>unfinished rebase</b> process:").br()
|
||||
.append(HtmlChunk.text(rootPath).bold()).br()
|
||||
.appendRaw("You probably want to <b>continue rebase</b> instead of committing.").br()
|
||||
.append("Committing during rebase may lead to the commit loss.").br()
|
||||
.appendLink("https://www.kernel.org/pub/software/scm/git/docs/git-rebase.html", "Read more about Git rebase").toString();
|
||||
} else {
|
||||
title = "Commit in Detached HEAD";
|
||||
message = messageCommonStart + " is in the <b>detached HEAD</b> state: <br/>" +
|
||||
"<b>" + rootPath + "</b><br>" +
|
||||
"You can look around, make experimental changes and commit them, but be sure to checkout a branch not to lose your work. <br/>" +
|
||||
"Otherwise you risk losing your changes. <br/>" +
|
||||
readMore("http://gitolite.com/detached-head.html", "Read more about detached HEAD");
|
||||
message = new HtmlBuilder()
|
||||
.appendRaw(messageCommonStart + " is in the <b>detached HEAD</b> state:").br()
|
||||
.append(HtmlChunk.text(rootPath).bold()).br()
|
||||
.append("You can look around, make experimental changes and commit them, but be sure to checkout a branch not to lose your work.").br()
|
||||
.append("Otherwise you risk losing your changes.").br()
|
||||
.appendLink("http://gitolite.com/detached-head.html", "Read more about detached HEAD").toString();
|
||||
}
|
||||
|
||||
DialogWrapper.DoNotAskOption dontAskAgain = new DialogWrapper.DoNotAskOption.Adapter() {
|
||||
@@ -337,11 +341,6 @@ public class GitCheckinHandlerFactory extends VcsCheckinHandlerFactory {
|
||||
return executor == null || executor instanceof GitCommitAndPushExecutor;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String readMore(@NotNull String link, @NotNull String message) {
|
||||
return String.format("<a href='%s'>%s</a>.", link, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans the Git roots, selected for commit, for the root which is on a detached HEAD.
|
||||
* Returns null, if all repositories are on the branch.
|
||||
|
||||
@@ -227,7 +227,7 @@ class GitBranchWorkerTest : GitPlatformTest() {
|
||||
assertDetachedState(second, "feature")
|
||||
|
||||
assertSuccessfulNotification("Checked out ${bcode("feature")} in community and contrib<br/>" +
|
||||
"Revision not found in ${project.stateStore.projectBasePath.fileName}<br><a href='rollback'>Rollback</a>")
|
||||
"Revision not found in ${project.stateStore.projectBasePath.fileName}<br><a href=\"rollback\">Rollback</a>")
|
||||
}
|
||||
|
||||
fun `test checkout with untracked files overwritten by checkout in first repo should show notification`() {
|
||||
@@ -343,7 +343,7 @@ class GitBranchWorkerTest : GitPlatformTest() {
|
||||
|
||||
fun `test agree to smart merge should smart merge`() {
|
||||
val localChanges = `agree to smart operation`("merge",
|
||||
"Merged <b><code>feature</code></b> to <b><code>master</code></b><br/><a href='delete'>Delete feature</a>")
|
||||
"Merged <b><code>feature</code></b> to <b><code>master</code></b><br/><a href=\"delete\">Delete feature</a>")
|
||||
|
||||
cd(last)
|
||||
val actual = cat(localChanges.first())
|
||||
@@ -653,7 +653,7 @@ class GitBranchWorkerTest : GitPlatformTest() {
|
||||
mergeBranch("master2", TestUiHandler())
|
||||
|
||||
assertSuccessfulNotification("Merged ${bcode("master2")} to ${bcode("master")}<br/>" +
|
||||
"<a href='delete'>Delete master2</a>")
|
||||
"<a href=\"delete\">Delete master2</a>")
|
||||
assertFile(last, "branch_file.txt", "branch content")
|
||||
assertFile(first, "branch_file.txt", "branch content")
|
||||
assertFile(second, "branch_file.txt", "branch content")
|
||||
@@ -709,7 +709,7 @@ class GitBranchWorkerTest : GitPlatformTest() {
|
||||
mergeBranch("master2", TestUiHandler())
|
||||
|
||||
assertNotNull("Success message wasn't shown", vcsNotifier.lastNotification)
|
||||
assertEquals("Success message is incorrect", "Already up-to-date<br/><a href='delete'>Delete master2</a>",
|
||||
assertEquals("Success message is incorrect", "Already up-to-date<br/><a href=\"delete\">Delete master2</a>",
|
||||
vcsNotifier.lastNotification.content)
|
||||
}
|
||||
|
||||
@@ -722,7 +722,7 @@ class GitBranchWorkerTest : GitPlatformTest() {
|
||||
|
||||
assertNotNull("Success message wasn't shown", vcsNotifier.lastNotification)
|
||||
assertEquals("Success message is incorrect",
|
||||
"Merged " + bcode("master2") + " to " + bcode("master") + "<br/><a href='delete'>Delete master2</a>",
|
||||
"Merged " + bcode("master2") + " to " + bcode("master") + "<br/><a href=\"delete\">Delete master2</a>",
|
||||
vcsNotifier.lastNotification.content)
|
||||
assertFile(first, "branch_file.txt", "branch content")
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.DialogWrapper;
|
||||
import com.intellij.openapi.ui.MessageDialogBuilder;
|
||||
import com.intellij.openapi.ui.Messages;
|
||||
import com.intellij.openapi.util.text.HtmlChunk;
|
||||
import com.intellij.openapi.vcs.VcsNotifier;
|
||||
import git4idea.i18n.GitBundle;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -98,9 +99,8 @@ public final class GithubNotifications {
|
||||
@NotificationContent @NotNull String message,
|
||||
@NotNull String url) {
|
||||
LOG.info(title + "; " + message + "; " + url);
|
||||
//noinspection HardCodedStringLiteral
|
||||
VcsNotifier.getInstance(project)
|
||||
.notifyImportantInfo(title, "<a href='" + url + "'>" + message + "</a>", NotificationListener.URL_OPENING_LISTENER);
|
||||
.notifyImportantInfo(title, HtmlChunk.link(url, message).toString(), NotificationListener.URL_OPENING_LISTENER);
|
||||
}
|
||||
|
||||
public static void showWarningURL(@NotNull Project project,
|
||||
|
||||
@@ -25,7 +25,7 @@ import com.intellij.openapi.module.ModuleManager;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.startup.StartupActivity;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.util.text.HtmlBuilder;
|
||||
import com.intellij.openapi.vfs.VfsUtil;
|
||||
import com.intellij.util.concurrency.AppExecutorUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -51,16 +51,16 @@ public class MvcProjectWithoutLibraryNotificator implements StartupActivity.Dumb
|
||||
final String name = framework.getFrameworkName();
|
||||
final Map<String, Runnable> actions = framework.createConfigureActions(module);
|
||||
|
||||
final StringBuilder content = new StringBuilder()
|
||||
.append("<html><body>")
|
||||
.append("Module ").append('\'').append(module.getName()).append('\'')
|
||||
.append(" has no ").append(name).append(" SDK.");
|
||||
if (!actions.isEmpty()) content.append("<br/>");
|
||||
content.append(StringUtil.join(actions.keySet(), actionName -> String.format("<a href='%s'>%s</a>", actionName, actionName), " "));
|
||||
content.append("</body></html>");
|
||||
HtmlBuilder builder = new HtmlBuilder();
|
||||
builder.append("Module '" + module.getName() + "' has no " + name + " SDK.");
|
||||
if (!actions.isEmpty()) builder.br();
|
||||
for (String actionName : actions.keySet()) {
|
||||
builder.appendLink(actionName, actionName).append(" ");
|
||||
}
|
||||
String message = builder.wrapWith("body").wrapWith("html").toString();
|
||||
|
||||
new Notification(
|
||||
name + ".Configure", name + " SDK not found", content.toString(), NotificationType.INFORMATION,
|
||||
name + ".Configure", name + " SDK not found", message, NotificationType.INFORMATION,
|
||||
new NotificationListener.Adapter() {
|
||||
@Override
|
||||
protected void hyperlinkActivated(@NotNull Notification notification, @NotNull HyperlinkEvent e) {
|
||||
|
||||
@@ -10,6 +10,7 @@ import com.intellij.dvcs.push.ui.VcsEditableTextComponent;
|
||||
import com.intellij.openapi.editor.event.DocumentEvent;
|
||||
import com.intellij.openapi.editor.event.DocumentListener;
|
||||
import com.intellij.openapi.ui.ValidationInfo;
|
||||
import com.intellij.openapi.util.text.HtmlChunk;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.ui.ColoredTreeCellRenderer;
|
||||
import com.intellij.ui.SimpleTextAttributes;
|
||||
@@ -37,7 +38,7 @@ public class HgPushTargetPanel extends PushTargetPanel<HgTarget> {
|
||||
myBranchName = source.getBranch();
|
||||
final List<String> targetVariants = HgUtil.getTargetNames(repository);
|
||||
String defaultText = defaultTarget != null ? defaultTarget.getPresentation() : "";
|
||||
myTargetRenderedComponent = new VcsEditableTextComponent("<a href=''>" + defaultText + "</a>", null);
|
||||
myTargetRenderedComponent = new VcsEditableTextComponent(HtmlChunk.link("", defaultText).toString(), null);
|
||||
myDestTargetPanel = new PushTargetTextField(repository.getProject(), targetVariants, defaultText);
|
||||
add(myDestTargetPanel, BorderLayout.CENTER);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,10 @@ import com.intellij.openapi.editor.event.*;
|
||||
import com.intellij.openapi.editor.impl.FoldingPopupManager;
|
||||
import com.intellij.openapi.keymap.KeymapUtil;
|
||||
import com.intellij.openapi.util.Key;
|
||||
import com.intellij.openapi.util.text.HtmlBuilder;
|
||||
import com.intellij.openapi.util.text.HtmlChunk;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.ui.ColorUtil;
|
||||
import com.intellij.ui.HintHint;
|
||||
import com.intellij.ui.LightweightHint;
|
||||
import com.intellij.util.Alarm;
|
||||
@@ -106,9 +109,9 @@ public final class EditPropertyValueTooltipManager implements EditorMouseListene
|
||||
if (action == null) return null;
|
||||
String text = action.getTemplateText();
|
||||
if (text == null) return null;
|
||||
StringBuilder b = new StringBuilder().append("<a href='").append(href).append("'>").append(text).append("</a> <span style='color:#");
|
||||
UIUtil.appendColor(UIUtil.getContextHelpForeground(), b);
|
||||
return b.append("'>").append(KeymapUtil.getFirstKeyboardShortcutText(action)).append("</span>").toString();
|
||||
HtmlChunk.Element shortcut = HtmlChunk.span("color:" + ColorUtil.toHtmlColor(UIUtil.getContextHelpForeground()))
|
||||
.addText(KeymapUtil.getFirstKeyboardShortcutText(action));
|
||||
return new HtmlBuilder().appendLink(href, text).append(" ").append(shortcut).toString();
|
||||
}
|
||||
|
||||
public static LightweightHint showTooltip(@NotNull Editor editor, @NotNull JComponent component, boolean tenacious) {
|
||||
|
||||
@@ -13,6 +13,7 @@ import com.intellij.openapi.progress.ProcessCanceledException;
|
||||
import com.intellij.openapi.progress.ProgressManager;
|
||||
import com.intellij.openapi.util.AtomicNullableLazyValue;
|
||||
import com.intellij.openapi.util.NullableLazyValue;
|
||||
import com.intellij.openapi.util.text.HtmlChunk;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
@@ -111,7 +112,7 @@ public class ShDocumentationProvider implements DocumentationProvider {
|
||||
while (m.find()) {
|
||||
if (m.groupCount() > 0) {
|
||||
String url = m.group(0);
|
||||
m.appendReplacement(sb, "<a href='" + url + "'>" + url + "</a>");
|
||||
m.appendReplacement(sb, HtmlChunk.link(url, url).toString());
|
||||
}
|
||||
}
|
||||
m.appendTail(sb);
|
||||
|
||||
Reference in New Issue
Block a user