From cb2d1406cceac5fd582b1b52547a819b50144094 Mon Sep 17 00:00:00 2001
From: shuzijun
Date: Mon, 16 Aug 2021 17:22:29 +0800
Subject: [PATCH] Optimization view
---
.github/FUNDING.yml | 12 +
README.md | 5 +-
README_ZH.md | 5 +-
build.gradle | 7 +-
gradle.properties | 2 +-
.../plugin/actions/toolbar/DonateAction.java | 17 +
.../plugin/editor/BaseController.java | 103 ++++
.../plugin/editor/ContentProvider.java | 16 +
.../leetcode/plugin/editor/LCVFileType.java | 41 ++
.../leetcode/plugin/editor/LCVLanguage.java | 15 +
.../leetcode/plugin/editor/LCVPanel.java | 158 ++++++
.../leetcode/plugin/editor/LCVPreview.java | 213 +++++++++
.../leetcode/plugin/editor/LCVProvider.java | 39 ++
.../plugin/editor/PreviewStaticServer.java | 63 +++
.../editor/ProxyLoadHtmlResourceHandler.java | 80 ++++
.../plugin/editor/QuestionEditorProvider.java | 6 +-
.../editor/QuestionEditorWithPreview.java | 15 +-
.../plugin/editor/ResourcesController.java | 48 ++
.../plugin/editor/SplitFileEditor.java | 451 ++++++++++++++++++
.../editor/SplitTextEditorProvider.java | 139 ++++++
.../plugin/manager/ArticleManager.java | 104 +---
.../leetcode/plugin/manager/CodeManager.java | 32 +-
.../leetcode/plugin/model/Config.java | 66 ++-
.../leetcode/plugin/model/PluginConstant.java | 3 +
.../leetcode/plugin/setting/SettingUI.form | 20 +-
.../leetcode/plugin/setting/SettingUI.java | 6 +
.../leetcode/plugin/utils/FileUtils.java | 8 +
.../plugin/utils/doc/CleanExtensions.java | 35 ++
.../plugin/utils/doc/CleanMarkdown.java | 34 ++
.../plugin/utils/doc/CleanNodeFormatter.java | 165 +++++++
.../plugin/utils/doc/InlineLaTexNode.java | 37 ++
.../utils/doc/InlineLaTexProcessor.java | 76 +++
.../leetcode/plugin/window/JcefLogin.java | 3 +-
.../leetcode/plugin/window/WindowFactory.java | 6 +-
src/main/java/icons/LeetCodeEditorIcons.java | 58 +--
src/main/resources/META-INF/plugin.xml | 14 +-
src/main/resources/META-INF/pluginIcon.svg | 21 +
src/main/resources/i18n/info.properties | 4 +-
src/main/resources/i18n/info_zh.properties | 8 +-
src/main/resources/icons/donate.svg | 7 +
src/main/resources/icons/donate_dark.svg | 7 +
.../resources/icons/emperor_new_clothes.svg | 4 +
src/main/resources/icons/lcv.svg | 11 +
src/main/resources/icons/lcv_dark.svg | 11 +
src/main/resources/template/default.html | 58 +++
45 files changed, 2042 insertions(+), 191 deletions(-)
create mode 100644 .github/FUNDING.yml
create mode 100644 src/main/java/com/shuzijun/leetcode/plugin/actions/toolbar/DonateAction.java
create mode 100644 src/main/java/com/shuzijun/leetcode/plugin/editor/BaseController.java
create mode 100644 src/main/java/com/shuzijun/leetcode/plugin/editor/ContentProvider.java
create mode 100644 src/main/java/com/shuzijun/leetcode/plugin/editor/LCVFileType.java
create mode 100644 src/main/java/com/shuzijun/leetcode/plugin/editor/LCVLanguage.java
create mode 100644 src/main/java/com/shuzijun/leetcode/plugin/editor/LCVPanel.java
create mode 100644 src/main/java/com/shuzijun/leetcode/plugin/editor/LCVPreview.java
create mode 100644 src/main/java/com/shuzijun/leetcode/plugin/editor/LCVProvider.java
create mode 100644 src/main/java/com/shuzijun/leetcode/plugin/editor/PreviewStaticServer.java
create mode 100644 src/main/java/com/shuzijun/leetcode/plugin/editor/ProxyLoadHtmlResourceHandler.java
create mode 100644 src/main/java/com/shuzijun/leetcode/plugin/editor/ResourcesController.java
create mode 100644 src/main/java/com/shuzijun/leetcode/plugin/editor/SplitFileEditor.java
create mode 100644 src/main/java/com/shuzijun/leetcode/plugin/editor/SplitTextEditorProvider.java
create mode 100644 src/main/java/com/shuzijun/leetcode/plugin/utils/doc/CleanExtensions.java
create mode 100644 src/main/java/com/shuzijun/leetcode/plugin/utils/doc/CleanMarkdown.java
create mode 100644 src/main/java/com/shuzijun/leetcode/plugin/utils/doc/CleanNodeFormatter.java
create mode 100644 src/main/java/com/shuzijun/leetcode/plugin/utils/doc/InlineLaTexNode.java
create mode 100644 src/main/java/com/shuzijun/leetcode/plugin/utils/doc/InlineLaTexProcessor.java
create mode 100644 src/main/resources/META-INF/pluginIcon.svg
create mode 100644 src/main/resources/icons/donate.svg
create mode 100644 src/main/resources/icons/donate_dark.svg
create mode 100644 src/main/resources/icons/emperor_new_clothes.svg
create mode 100644 src/main/resources/icons/lcv.svg
create mode 100644 src/main/resources/icons/lcv_dark.svg
create mode 100644 src/main/resources/template/default.html
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 00000000..3d83af55
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,12 @@
+# These are supported funding model platforms
+
+github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
+patreon: # Replace with a single Patreon username
+open_collective: # Replace with a single Open Collective username
+ko_fi: # Replace with a single Ko-fi username
+tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
+community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
+liberapay: # Replace with a single Liberapay username
+issuehunt: # Replace with a single IssueHunt username
+otechie: # Replace with a single Otechie username
+custom: ['https://shuzijun.cn/donate.html']
diff --git a/README.md b/README.md
index c5b663f7..4416d538 100644
--- a/README.md
+++ b/README.md
@@ -24,8 +24,9 @@
### Installation([help](https://www.jetbrains.com/help/idea/2019.2/managing-plugins.html))
-- **Install via plug-in library** https://plugins.jetbrains.com/plugin/12132-leetcode-editor
-- **Install by downloading the file** https://github.com/shuzijun/leetcode-editor/releases
+- **Install via plug-in library** [leetcode-editor](https://plugins.jetbrains.com/plugin/12132-leetcode-editor)
+- **Install by downloading the file** [releases](https://github.com/shuzijun/leetcode-editor/releases)
+- **If you are willing to donate to this project, you can choose the pro version** [leetcode-editor-pro](https://plugins.jetbrains.com/plugin/17166-leetcode-editor-pro)
### Configuration (configuration for first installation)
diff --git a/README_ZH.md b/README_ZH.md
index ccf49dfb..1518ebcf 100644
--- a/README_ZH.md
+++ b/README_ZH.md
@@ -24,8 +24,9 @@
### 安装
-- **通过插件库安装** https://plugins.jetbrains.com/plugin/12132-leetcode-editor
-- **下载文件安装** https://github.com/shuzijun/leetcode-editor/releases
+- **通过插件库安装** [leetcode-editor](https://plugins.jetbrains.com/plugin/12132-leetcode-editor)
+- **下载文件安装** [releases](https://github.com/shuzijun/leetcode-editor/releases)
+- **如果您想捐助此项目,可以选择pro版本** [leetcode-editor-pro](https://plugins.jetbrains.com/plugin/17166-leetcode-editor-pro)
### 配置(第一次安装需要先配置)
diff --git a/build.gradle b/build.gradle
index 9af7f48c..5d6e299c 100644
--- a/build.gradle
+++ b/build.gradle
@@ -22,6 +22,8 @@ dependencies {
}
api 'org.scilab.forge:jlatexmath:1.0.7'
api 'org.apache.commons:commons-lang3:3.9'
+ api 'com.vladsch.flexmark:flexmark:0.62.2'
+ api 'com.vladsch.flexmark:flexmark-ext-attributes:0.62.2'
//api fileTree(dir: 'src/main/resources/lib', include: ['*.jar'])
}
@@ -37,7 +39,10 @@ intellij {
ideaDependencyCachePath = "$gradle.gradleUserHomeDir/caches/modules-2/files-2.1/com.jetbrains.intellij.idea"
/* localPath project.idea_local_path
alternativeIdePath project.idea_local_path*/
- plugins = project.intellij_plugins.split(",").toList()
+ /*plugins = project.intellij_plugins.split(",").toList()*/
+ runIde {
+ jvmArgs = ["-Dfile.encoding=utf-8"]
+ }
/* prepareSandbox {
from("src/main/natives" + (project.build_env.isEmpty() ? "" : "-") + project.build_env) { into(getPluginName() + '/natives') }
}*/
diff --git a/gradle.properties b/gradle.properties
index 85189b0c..ee56b54f 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,4 +1,4 @@
-plugin_version = 2021.1.0
+plugin_version = 7.0
idea_local_path =
# '' | windows | mac | linux
build_env =
diff --git a/src/main/java/com/shuzijun/leetcode/plugin/actions/toolbar/DonateAction.java b/src/main/java/com/shuzijun/leetcode/plugin/actions/toolbar/DonateAction.java
new file mode 100644
index 00000000..0bbe31be
--- /dev/null
+++ b/src/main/java/com/shuzijun/leetcode/plugin/actions/toolbar/DonateAction.java
@@ -0,0 +1,17 @@
+package com.shuzijun.leetcode.plugin.actions.toolbar;
+
+import com.intellij.ide.BrowserUtil;
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+
+
+/**
+ * @author shuzijun
+ */
+public class DonateAction extends AnAction {
+ @Override
+ public void actionPerformed(AnActionEvent anActionEvent) {
+ BrowserUtil.browse("https://shuzijun.cn/donate.html");
+ }
+
+}
diff --git a/src/main/java/com/shuzijun/leetcode/plugin/editor/BaseController.java b/src/main/java/com/shuzijun/leetcode/plugin/editor/BaseController.java
new file mode 100644
index 00000000..bea2959c
--- /dev/null
+++ b/src/main/java/com/shuzijun/leetcode/plugin/editor/BaseController.java
@@ -0,0 +1,103 @@
+package com.shuzijun.leetcode.plugin.editor;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.project.ProjectManager;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.http.*;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.io.Responses;
+
+import java.io.IOException;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author shuzijun
+ */
+public abstract class BaseController {
+
+ // every time the plugin starts up, assume resources could have been modified
+ protected static final long LAST_MODIFIED = System.currentTimeMillis();
+
+ public final void process(@NotNull QueryStringDecoder urlDecoder, @NotNull FullHttpRequest request, @NotNull ChannelHandlerContext context) throws IOException {
+ FullHttpResponse response;
+ try {
+ if (request.method() == HttpMethod.POST) {
+ response = post(urlDecoder, request, context);
+ } else if (request.method() == HttpMethod.GET) {
+ response = get(urlDecoder, request, context);
+ } else {
+ response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.NOT_FOUND);
+ }
+ } catch (Throwable t) {
+ response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.INTERNAL_SERVER_ERROR, Unpooled.wrappedBuffer(t.getMessage().getBytes(StandardCharsets.UTF_8)));
+ }
+ Responses.send(response, context.channel(), request);
+ if (response.content() != Unpooled.EMPTY_BUFFER) {
+ try {
+ response.release();
+ } catch (Exception ignore) {
+ }
+ }
+ }
+
+ public FullHttpResponse get(@NotNull QueryStringDecoder urlDecoder, @NotNull FullHttpRequest request, @NotNull ChannelHandlerContext context) throws IOException {
+ return new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.NOT_FOUND);
+ }
+
+ public FullHttpResponse post(@NotNull QueryStringDecoder urlDecoder, @NotNull FullHttpRequest request, @NotNull ChannelHandlerContext context) throws IOException {
+ return new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.NOT_FOUND);
+ }
+
+ public abstract String getControllerPath();
+
+ protected String getResourceName(QueryStringDecoder urlDecoder) {
+ return urlDecoder.path().substring(PreviewStaticServer.PREFIX.length() + getControllerPath().length());
+ }
+
+ protected String getParameter(@NotNull QueryStringDecoder urlDecoder, @NotNull String parameter) {
+ List parameters = urlDecoder.parameters().get(parameter);
+ if (parameters == null || parameters.size() != 1) {
+ return null;
+ }
+ return URLDecoder.decode(parameters.get(0), StandardCharsets.UTF_8);
+ }
+
+ protected FullHttpResponse fillHtmlResponse(String content) {
+ FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.wrappedBuffer(content.getBytes(StandardCharsets.UTF_8)));
+ response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain;charset=UTF-8");
+ response.headers().set(HttpHeaderNames.CACHE_CONTROL, "max-age=5, private, must-revalidate");
+ response.headers().set("Referrer-Policy", "no-referrer");
+ return response;
+ }
+
+ protected FullHttpResponse fillJsonResponse(String content) {
+ FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.wrappedBuffer(content.getBytes(StandardCharsets.UTF_8)));
+ response.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json;charset=UTF-8");
+ response.headers().set(HttpHeaderNames.CACHE_CONTROL, "max-age=5, private, must-revalidate");
+ response.headers().set("Referrer-Policy", "no-referrer");
+ return response;
+ }
+
+ public void addRoute(Map route) {
+ route.put(PreviewStaticServer.PREFIX + getControllerPath(), this);
+ }
+
+ protected Project getProject(String projectNameParameter, String projectUrlParameter) {
+ Project project = null;
+ for (Project p : ProjectManager.getInstance().getOpenProjects()) {
+ if ((projectNameParameter != null && projectNameParameter.equals(p.getName()))
+ || (projectUrlParameter != null && projectUrlParameter.equals(p.getPresentableUrl()))) {
+ project = p;
+ break;
+ }
+ }
+
+ return project;
+ }
+
+
+}
diff --git a/src/main/java/com/shuzijun/leetcode/plugin/editor/ContentProvider.java b/src/main/java/com/shuzijun/leetcode/plugin/editor/ContentProvider.java
new file mode 100644
index 00000000..b074a18d
--- /dev/null
+++ b/src/main/java/com/shuzijun/leetcode/plugin/editor/ContentProvider.java
@@ -0,0 +1,16 @@
+package com.shuzijun.leetcode.plugin.editor;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author shuzijun
+ */
+public class ContentProvider extends LCVProvider{
+
+ @Override
+ public boolean accept(@NotNull Project project, @NotNull VirtualFile file) {
+ return true;
+ }
+}
diff --git a/src/main/java/com/shuzijun/leetcode/plugin/editor/LCVFileType.java b/src/main/java/com/shuzijun/leetcode/plugin/editor/LCVFileType.java
new file mode 100644
index 00000000..66189a42
--- /dev/null
+++ b/src/main/java/com/shuzijun/leetcode/plugin/editor/LCVFileType.java
@@ -0,0 +1,41 @@
+package com.shuzijun.leetcode.plugin.editor;
+
+import com.intellij.openapi.fileTypes.LanguageFileType;
+import com.shuzijun.leetcode.plugin.model.PluginConstant;
+import icons.LeetCodeEditorIcons;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+
+public class LCVFileType extends LanguageFileType {
+ public static final LCVFileType INSTANCE = new LCVFileType();
+
+ private LCVFileType() {
+ super(LCVLanguage.INSTANCE);
+ }
+
+ @NotNull
+ @Override
+ public String getName() {
+ return PluginConstant.LEETCODE_EDITOR_VIEW+"Doc";
+ }
+
+ @NotNull
+ @Override
+ public String getDescription() {
+ return PluginConstant.LEETCODE_EDITOR_VIEW;
+ }
+
+ @NotNull
+ @Override
+ public String getDefaultExtension() {
+ return PluginConstant.LEETCODE_EDITOR_VIEW;
+ }
+
+ @Nullable
+ @Override
+ public Icon getIcon() {
+ return LeetCodeEditorIcons.LCV;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/shuzijun/leetcode/plugin/editor/LCVLanguage.java b/src/main/java/com/shuzijun/leetcode/plugin/editor/LCVLanguage.java
new file mode 100644
index 00000000..91cbb068
--- /dev/null
+++ b/src/main/java/com/shuzijun/leetcode/plugin/editor/LCVLanguage.java
@@ -0,0 +1,15 @@
+package com.shuzijun.leetcode.plugin.editor;
+
+import com.intellij.lang.Language;
+import com.shuzijun.leetcode.plugin.model.PluginConstant;
+
+public class LCVLanguage extends Language {
+
+ public static final String LANGUAGE_NAME = PluginConstant.LEETCODE_EDITOR_VIEW+"Doc";
+
+ public static final LCVLanguage INSTANCE = new LCVLanguage();
+
+ protected LCVLanguage() {
+ super(LANGUAGE_NAME);
+ }
+}
diff --git a/src/main/java/com/shuzijun/leetcode/plugin/editor/LCVPanel.java b/src/main/java/com/shuzijun/leetcode/plugin/editor/LCVPanel.java
new file mode 100644
index 00000000..3f3f461e
--- /dev/null
+++ b/src/main/java/com/shuzijun/leetcode/plugin/editor/LCVPanel.java
@@ -0,0 +1,158 @@
+package com.shuzijun.leetcode.plugin.editor;
+
+import com.intellij.ide.BrowserUtil;
+import com.intellij.notification.Notification;
+import com.intellij.notification.NotificationType;
+import com.intellij.notification.Notifications;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.fileEditor.FileEditor;
+import com.intellij.openapi.fileEditor.FileEditorManager;
+import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.openapi.fileTypes.FileTypes;
+import com.intellij.openapi.fileTypes.ex.FileTypeChooser;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.ui.jcef.JCEFHtmlPanel;
+import com.intellij.util.io.HttpRequests;
+import com.intellij.util.io.URLUtil;
+import com.intellij.util.ui.UIUtil;
+import com.shuzijun.leetcode.plugin.model.PluginConstant;
+import com.shuzijun.leetcode.plugin.utils.FileUtils;
+import io.netty.handler.codec.http.HttpHeaderNames;
+import org.apache.commons.lang.StringUtils;
+import org.cef.browser.CefBrowser;
+import org.cef.browser.CefFrame;
+import org.cef.handler.*;
+import org.cef.misc.BoolRef;
+import org.cef.network.CefRequest;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+
+/**
+ * @author shuzijun
+ */
+public class LCVPanel extends JCEFHtmlPanel {
+
+ private static final Logger LOG = Logger.getInstance(LCVPanel.class);
+
+ private final CefRequestHandler requestHandler;
+ private final CefLifeSpanHandler lifeSpanHandler;
+
+ private final String url;
+ private final Project project;
+ private final List iframe = new ArrayList<>();
+ private static final List headers = Arrays.asList(HttpHeaderNames.CONTENT_SECURITY_POLICY.toString(), HttpHeaderNames.CONTENT_ENCODING.toString()
+ , HttpHeaderNames.CONTENT_LENGTH.toString());
+
+ public LCVPanel(@Nullable String url, Project project) {
+ super("about:blank");
+ this.url = url;
+ this.project = project;
+ getJBCefClient().addRequestHandler(requestHandler = new CefRequestHandlerAdapter() {
+ @Override
+ public boolean onBeforeBrowse(CefBrowser browser, CefFrame frame, CefRequest request, boolean user_gesture, boolean is_redirect) {
+ String requestUrl = request.getURL();
+ if (requestUrl.startsWith(url)) {
+ return false;
+ } else if (!user_gesture) {
+ iframe.add(requestUrl);
+ return false;
+ } else {
+ openUrl(URLDecoder.decode(requestUrl, StandardCharsets.UTF_8));
+ return true;
+ }
+ }
+
+ @Override
+ public CefResourceRequestHandler getResourceRequestHandler(CefBrowser browser, CefFrame frame, CefRequest request, boolean isNavigation, boolean isDownload, String requestInitiator, BoolRef disableDefaultHandling) {
+ String requestUrl = request.getURL();
+ if (!iframe.contains(requestUrl)) {
+ return null;
+ }
+
+ return new CefResourceRequestHandlerAdapter() {
+
+ @Override
+ public CefResourceHandler getResourceHandler(CefBrowser browser, CefFrame frame, CefRequest request) {
+ try {
+ return HttpRequests.request(request.getURL())
+ .throwStatusCodeException(false)
+ .connect(new HttpRequests.RequestProcessor() {
+ @Override
+ public CefResourceHandler process(HttpRequests.@NotNull Request request) throws IOException {
+ HttpURLConnection urlConnection = (HttpURLConnection) request.getConnection();
+ Map header = new HashMap<>();
+ urlConnection.getHeaderFields().forEach((key, values) -> {
+ if (key != null && values != null && !headers.contains(key.toLowerCase())) {
+ header.put(key, StringUtils.join(values.toArray(), ";"));
+ }
+ });
+ return new ProxyLoadHtmlResourceHandler(request.readString(), header, urlConnection.getResponseCode());
+ }
+ });
+ } catch (IOException io) {
+
+ return null;
+ }
+ }
+ };
+ }
+ }, getCefBrowser());
+ getJBCefClient().addLifeSpanHandler(lifeSpanHandler = new CefLifeSpanHandlerAdapter() {
+ @Override
+ public boolean onBeforePopup(CefBrowser browser, CefFrame frame, String target_url, String target_frame_name) {
+ if (!target_url.startsWith(url)) {
+ openUrl(URLDecoder.decode(target_url, StandardCharsets.UTF_8));
+ }
+ return true;
+ }
+ }, getCefBrowser());
+ }
+
+ @Override
+ public void dispose() {
+ getJBCefClient().removeRequestHandler(requestHandler, getCefBrowser());
+ getJBCefClient().removeLifeSpanHandler(lifeSpanHandler, getCefBrowser());
+ super.dispose();
+ }
+
+ private void openUrl(String url) {
+ if (url.startsWith(URLUtil.FILE_PROTOCOL)) {
+ File file = new File(url.substring((URLUtil.FILE_PROTOCOL + URLUtil.SCHEME_SEPARATOR + FileUtils.separator()).length()));
+ if (!file.exists()) {
+ Notifications.Bus.notify(new Notification(PluginConstant.NOTIFICATION_GROUP, "Cannot Open File", file.getPath() + " not exist", NotificationType.INFORMATION), project);
+ } else if (file.isDirectory()) {
+ Notifications.Bus.notify(new Notification(PluginConstant.NOTIFICATION_GROUP, "Cannot Open Directory", file.getPath() + " is a directory", NotificationType.INFORMATION), project);
+ } else {
+ ApplicationManager.getApplication().invokeLater(() -> {
+ VirtualFile vf = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file);
+ FileEditor[] editors = FileEditorManager.getInstance(project).openFile(vf, false);
+ if (editors == null || editors.length == 0) {
+ FileType fileType = FileTypeChooser.getKnownFileTypeOrAssociate(vf, project);
+ if (fileType == null || fileType == FileTypes.UNKNOWN) {
+ return;
+ } else {
+ FileEditorManager.getInstance(project).openFile(vf, false);
+ }
+ }
+ });
+ }
+ } else {
+ BrowserUtil.browse(url);
+ }
+ }
+
+ public void updateStyle(String style) {
+ getCefBrowser().executeJavaScript(
+ "updateStyle('" + style + "'," + UIUtil.isUnderDarcula() + ");", getCefBrowser().getURL(), 0);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/shuzijun/leetcode/plugin/editor/LCVPreview.java b/src/main/java/com/shuzijun/leetcode/plugin/editor/LCVPreview.java
new file mode 100644
index 00000000..dd635149
--- /dev/null
+++ b/src/main/java/com/shuzijun/leetcode/plugin/editor/LCVPreview.java
@@ -0,0 +1,213 @@
+package com.shuzijun.leetcode.plugin.editor;
+
+import com.google.common.net.UrlEscapers;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.editor.colors.EditorColors;
+import com.intellij.openapi.editor.colors.EditorColorsListener;
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.editor.colors.EditorColorsScheme;
+import com.intellij.openapi.editor.colors.impl.EditorColorsSchemeImpl;
+import com.intellij.openapi.editor.markup.TextAttributes;
+import com.intellij.openapi.fileEditor.FileDocumentManager;
+import com.intellij.openapi.fileEditor.FileEditor;
+import com.intellij.openapi.fileEditor.FileEditorLocation;
+import com.intellij.openapi.fileEditor.FileEditorState;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Disposer;
+import com.intellij.openapi.util.UserDataHolderBase;
+import com.intellij.openapi.util.io.FileUtilRt;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.ui.JBColor;
+import com.intellij.ui.components.JBLabel;
+import com.intellij.util.Url;
+import com.intellij.util.Urls;
+import com.intellij.util.io.URLUtil;
+import com.intellij.util.messages.MessageBusConnection;
+import com.intellij.util.ui.UIUtil;
+import com.shuzijun.leetcode.plugin.model.PluginConstant;
+import com.shuzijun.leetcode.plugin.utils.FileUtils;
+import com.shuzijun.leetcode.plugin.utils.PropertiesUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.ide.BuiltInServerManager;
+
+import javax.swing.*;
+import java.awt.*;
+import java.beans.PropertyChangeListener;
+import java.io.IOException;
+import java.io.InputStream;
+import java.text.DecimalFormat;
+
+/**
+ * @author shuzijun
+ */
+public class LCVPreview extends UserDataHolderBase implements FileEditor {
+
+ private final Project myProject;
+ private final VirtualFile myFile;
+ private final Document myDocument;
+
+ private final JPanel myHtmlPanelWrapper;
+ private final LCVPanel myPanel;
+
+ private final Url servicePath = BuiltInServerManager.getInstance().addAuthToken(Urls.parseEncoded("http://localhost:" + BuiltInServerManager.getInstance().getPort() + PreviewStaticServer.PREFIX));
+ private final String templateHtmlFile = "template/default.html";
+ private final boolean isPresentableUrl;
+
+ public LCVPreview(@NotNull Project project, @NotNull VirtualFile file) {
+ myProject = project;
+ myFile = file;
+ myDocument = FileDocumentManager.getInstance().getDocument(myFile);
+ myHtmlPanelWrapper = new JPanel(new BorderLayout());
+ isPresentableUrl = project.getPresentableUrl() != null;
+ String url = UrlEscapers.urlFragmentEscaper().escape(URLUtil.FILE_PROTOCOL + URLUtil.SCHEME_SEPARATOR + FileUtils.separator() + myFile.getPath());
+ LCVPanel tempPanel = null;
+ try {
+ tempPanel = new LCVPanel(url, project);
+ tempPanel.loadHTML(createHtml(isPresentableUrl), url);
+ myHtmlPanelWrapper.add(tempPanel.getComponent(), BorderLayout.CENTER);
+ } catch (IllegalStateException e) {
+ myHtmlPanelWrapper.add(new JBLabel(e.getMessage()), BorderLayout.CENTER);
+ }
+ myPanel = tempPanel;
+ myHtmlPanelWrapper.repaint();
+ MessageBusConnection settingsConnection = ApplicationManager.getApplication().getMessageBus().connect(this);
+ settingsConnection.subscribe(EditorColorsManager.TOPIC, new EditorColorsListener() {
+ @Override
+ public void globalSchemeChange(@Nullable EditorColorsScheme scheme) {
+ myPanel.updateStyle(getStyle(false));
+ }
+ });
+ }
+
+ @Override
+ public @NotNull JComponent getComponent() {
+ return myHtmlPanelWrapper;
+ }
+
+ @Override
+ public @Nullable JComponent getPreferredFocusedComponent() {
+ return myPanel != null ? myPanel.getComponent() : null;
+ }
+
+ @Override
+ public @NotNull String getName() {
+ return PluginConstant.LEETCODE_EDITOR_VIEW;
+ }
+
+ @Override
+ public void setState(@NotNull FileEditorState state) {
+
+ }
+
+ @Override
+ public boolean isModified() {
+ return false;
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public void addPropertyChangeListener(@NotNull PropertyChangeListener listener) {
+
+ }
+
+ @Override
+ public void removePropertyChangeListener(@NotNull PropertyChangeListener listener) {
+
+ }
+
+ @Override
+ public @Nullable FileEditorLocation getCurrentLocation() {
+ return null;
+ }
+
+ @Override
+ public void dispose() {
+ if (myPanel != null) {
+ Disposer.dispose(myPanel);
+ }
+ }
+
+ private String createHtml(boolean isPresentableUrl) {
+ InputStream inputStream = null;
+
+ try {
+
+ inputStream = PreviewStaticServer.class.getResourceAsStream("/" + templateHtmlFile);
+
+ String template = new String(FileUtilRt.loadBytes(inputStream));
+ return template.replace("{{service}}", servicePath.getScheme() + URLUtil.SCHEME_SEPARATOR + servicePath.getAuthority() + servicePath.getPath())
+ .replace("{{serverToken}}", StringUtils.isNotBlank(servicePath.getParameters()) ? servicePath.getParameters().substring(1) : "")
+ .replace("{{fileValue}}", FileDocumentManager.getInstance().getDocument(myFile).getText())
+ .replace("{{Lang}}", PropertiesUtils.getInfo("Lang"))
+ .replace("{{darcula}}", UIUtil.isUnderDarcula() + "")
+ .replace("{{ideStyle}}", getStyle(true))
+ ;
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ } finally {
+ if (inputStream != null) {
+ try {
+ inputStream.close();
+ } catch (IOException ignore) {
+ }
+ }
+ }
+ }
+
+ @Override
+ public @Nullable VirtualFile getFile() {
+ return myFile;
+ }
+
+
+ private String getStyle(boolean isTag) {
+ try {
+ EditorColorsSchemeImpl editorColorsScheme = (EditorColorsSchemeImpl) EditorColorsManager.getInstance().getGlobalScheme();
+ Color defaultBackground = editorColorsScheme.getDefaultBackground();
+
+ Color scrollbarThumbColor = EditorColors.SCROLLBAR_THUMB_COLOR.getDefaultColor();
+ if (editorColorsScheme.getColor(EditorColors.SCROLLBAR_THUMB_COLOR) != null) {
+ scrollbarThumbColor = editorColorsScheme.getColor(EditorColors.SCROLLBAR_THUMB_COLOR);
+ }
+ TextAttributes textAttributes = editorColorsScheme.getDirectlyDefinedAttributes().get("TEXT");
+ Color text = null;
+ if (textAttributes != null) {
+ text = textAttributes.getForegroundColor();
+ }
+ String fontFamily = "font-family:\""+editorColorsScheme.getEditorFontName()+"\",\"Helvetica Neue\",\"Luxi Sans\",\"DejaVu Sans\"," +
+ "\"Hiragino Sans GB\",\"Microsoft Yahei\",sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\",\"Noto Color Emoji\",\"Segoe UI Symbol\"," +
+ "\"Android Emoji\",\"EmojiSymbols\";";
+ StringBuilder sb = new StringBuilder(isTag ? "" : "");
+ return sb.toString();
+ } catch (Exception e) {
+ return "";
+ }
+
+ }
+
+ private String toHexColor(Color color) {
+ DecimalFormat df = new DecimalFormat("0.00");
+ return String.format("rgba(%s,%s,%s,%s)", color.getRed(), color.getGreen(), color.getBlue(), df.format(color.getAlpha() / (float) 255));
+ }
+
+}
diff --git a/src/main/java/com/shuzijun/leetcode/plugin/editor/LCVProvider.java b/src/main/java/com/shuzijun/leetcode/plugin/editor/LCVProvider.java
new file mode 100644
index 00000000..34acc0b2
--- /dev/null
+++ b/src/main/java/com/shuzijun/leetcode/plugin/editor/LCVProvider.java
@@ -0,0 +1,39 @@
+package com.shuzijun.leetcode.plugin.editor;
+
+import com.intellij.openapi.fileEditor.FileEditor;
+import com.intellij.openapi.fileEditor.FileEditorPolicy;
+import com.intellij.openapi.fileEditor.WeighedFileEditorProvider;
+import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.ui.jcef.JBCefApp;
+import com.shuzijun.leetcode.plugin.model.PluginConstant;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author shuzijun
+ */
+public class LCVProvider extends WeighedFileEditorProvider {
+
+
+ @Override
+ public boolean accept(@NotNull Project project, @NotNull VirtualFile file) {
+ FileType fileType = file.getFileType();
+ return fileType == LCVFileType.INSTANCE && JBCefApp.isSupported();
+ }
+
+ @Override
+ public @NotNull FileEditor createEditor(@NotNull Project project, @NotNull VirtualFile file) {
+ return new LCVPreview(project, file);
+ }
+
+ @Override
+ public @NotNull String getEditorTypeId() {
+ return PluginConstant.LEETCODE_EDITOR_VIEW + " view";
+ }
+
+ @Override
+ public @NotNull FileEditorPolicy getPolicy() {
+ return FileEditorPolicy.HIDE_DEFAULT_EDITOR;
+ }
+}
diff --git a/src/main/java/com/shuzijun/leetcode/plugin/editor/PreviewStaticServer.java b/src/main/java/com/shuzijun/leetcode/plugin/editor/PreviewStaticServer.java
new file mode 100644
index 00000000..3b91165a
--- /dev/null
+++ b/src/main/java/com/shuzijun/leetcode/plugin/editor/PreviewStaticServer.java
@@ -0,0 +1,63 @@
+package com.shuzijun.leetcode.plugin.editor;
+
+import com.intellij.openapi.diagnostic.Logger;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.http.FullHttpRequest;
+import io.netty.handler.codec.http.HttpMethod;
+import io.netty.handler.codec.http.HttpRequest;
+import io.netty.handler.codec.http.QueryStringDecoder;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.ide.HttpRequestHandler;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author shuzijun
+ */
+public class PreviewStaticServer extends HttpRequestHandler {
+
+ private static final Logger LOG = Logger.getInstance(PreviewStaticServer.class);
+
+ public static final String PREFIX = "/3e41525e-e74a-4590-ba1d-982b8f638478/";
+
+ public static final Map route = new HashMap<>();
+ static {
+ new ResourcesController().addRoute(route);
+ }
+
+ public static PreviewStaticServer getInstance() {
+ return HttpRequestHandler.Companion.getEP_NAME().findExtension(PreviewStaticServer.class);
+ }
+
+ @Override
+ public boolean isAccessible(@NotNull HttpRequest request) {
+ return true;
+ }
+
+ @Override
+ public boolean isSupported(@NotNull FullHttpRequest request) {
+ return (request.method() == HttpMethod.GET || request.method() == HttpMethod.HEAD || request.method() == HttpMethod.POST) && request.uri().startsWith(PREFIX);
+ }
+
+ @Override
+ public boolean process(@NotNull QueryStringDecoder urlDecoder,
+ @NotNull FullHttpRequest request,
+ @NotNull ChannelHandlerContext context) throws IOException {
+ final String path = urlDecoder.path();
+ if (!path.startsWith(PREFIX)) {
+ throw new IllegalStateException("prefix should have been checked by #isSupported");
+ }
+
+ for (String controllerPath : route.keySet()) {
+ if (path.startsWith(controllerPath)) {
+ route.get(controllerPath).process(urlDecoder, request, context);
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/shuzijun/leetcode/plugin/editor/ProxyLoadHtmlResourceHandler.java b/src/main/java/com/shuzijun/leetcode/plugin/editor/ProxyLoadHtmlResourceHandler.java
new file mode 100644
index 00000000..00796051
--- /dev/null
+++ b/src/main/java/com/shuzijun/leetcode/plugin/editor/ProxyLoadHtmlResourceHandler.java
@@ -0,0 +1,80 @@
+package com.shuzijun.leetcode.plugin.editor;
+
+import com.intellij.openapi.diagnostic.Logger;
+import io.netty.handler.codec.http.HttpHeaderNames;
+import org.cef.callback.CefCallback;
+import org.cef.handler.CefResourceHandlerAdapter;
+import org.cef.misc.IntRef;
+import org.cef.misc.StringRef;
+import org.cef.network.CefRequest;
+import org.cef.network.CefResponse;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+
+/**
+ * @author shuzijun
+ */
+public class ProxyLoadHtmlResourceHandler extends CefResourceHandlerAdapter {
+ private static final Logger LOG = Logger.getInstance(ProxyLoadHtmlResourceHandler.class.getName());
+
+ @NotNull
+ private final InputStream myInputStream;
+ private final Map header ;
+ private final int status;
+
+ public ProxyLoadHtmlResourceHandler(@NotNull String html, Map header, int status) {
+ myInputStream = new ByteArrayInputStream(html.getBytes(StandardCharsets.UTF_8));
+ this.header = header;
+ this.status = status;
+ }
+
+ @Override
+ public boolean processRequest(@NotNull CefRequest request, @NotNull CefCallback callback) {
+ callback.Continue();
+ return true;
+ }
+
+ @Override
+ public void getResponseHeaders(@NotNull CefResponse response, IntRef response_length, StringRef redirectUrl) {
+ header.forEach((key,value) -> {
+ response.setHeaderByName(key,value,true);
+ if(HttpHeaderNames.CONTENT_TYPE.toString().equals(key.toLowerCase())){
+ if(value.indexOf(";")>0){
+ response.setMimeType(value.substring(0,value.indexOf(";")));
+ }else {
+ response.setMimeType(value);
+ }
+ }
+ });
+ response.setStatus(status);
+ }
+
+ @Override
+ public boolean readResponse(byte@NotNull[] data_out, int bytes_to_read, IntRef bytes_read, CefCallback callback) {
+ try {
+ int availableSize = myInputStream.available();
+ if (availableSize > 0) {
+ int bytesToRead = Math.min(bytes_to_read, availableSize);
+ bytesToRead = myInputStream.read(data_out, 0, bytesToRead);
+ bytes_read.set(bytesToRead);
+ return true;
+ }
+ }
+ catch (IOException e) {
+ LOG.error(e);
+ }
+ bytes_read.set(0);
+ try {
+ myInputStream.close();
+ }
+ catch (IOException e) {
+ LOG.error(e);
+ }
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/shuzijun/leetcode/plugin/editor/QuestionEditorProvider.java b/src/main/java/com/shuzijun/leetcode/plugin/editor/QuestionEditorProvider.java
index 4ff01b2b..965e0466 100644
--- a/src/main/java/com/shuzijun/leetcode/plugin/editor/QuestionEditorProvider.java
+++ b/src/main/java/com/shuzijun/leetcode/plugin/editor/QuestionEditorProvider.java
@@ -11,8 +11,6 @@
import com.shuzijun.leetcode.plugin.setting.PersistentConfig;
import com.shuzijun.leetcode.plugin.setting.ProjectConfig;
import org.apache.commons.lang3.StringUtils;
-import org.intellij.plugins.markdown.ui.preview.MarkdownPreviewFileEditorProvider;
-import org.intellij.plugins.markdown.ui.split.SplitTextEditorProvider;
import org.jetbrains.annotations.NotNull;
import java.io.File;
@@ -22,10 +20,8 @@
*/
public class QuestionEditorProvider extends SplitTextEditorProvider {
-
-
public QuestionEditorProvider() {
- super(new PsiAwareTextEditorProvider(), new MarkdownPreviewFileEditorProvider());
+ super(new PsiAwareTextEditorProvider(), new ContentProvider());
}
@Override
diff --git a/src/main/java/com/shuzijun/leetcode/plugin/editor/QuestionEditorWithPreview.java b/src/main/java/com/shuzijun/leetcode/plugin/editor/QuestionEditorWithPreview.java
index d410b79f..359b4f10 100644
--- a/src/main/java/com/shuzijun/leetcode/plugin/editor/QuestionEditorWithPreview.java
+++ b/src/main/java/com/shuzijun/leetcode/plugin/editor/QuestionEditorWithPreview.java
@@ -2,35 +2,24 @@
import com.intellij.openapi.actionSystem.ActionGroup;
import com.intellij.openapi.actionSystem.ActionManager;
-import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileEditor.FileEditor;
import com.intellij.openapi.fileEditor.TextEditor;
import com.intellij.openapi.fileEditor.TextEditorWithPreview;
import com.intellij.openapi.util.Key;
import com.shuzijun.leetcode.plugin.model.PluginConstant;
-import org.intellij.plugins.markdown.ui.preview.MarkdownPreviewFileEditor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
-import java.lang.reflect.Method;
-
/**
* @author shuzijun
*/
public class QuestionEditorWithPreview extends TextEditorWithPreview {
- public static final Key PARENT_SPLIT_EDITOR_KEY = Key.create("Question Split");
+ public static final Key PARENT_SPLIT_EDITOR_KEY = Key.create(PluginConstant.PLUGIN_ID + "Question Split");
public QuestionEditorWithPreview(@NotNull TextEditor editor, @NotNull FileEditor preview) {
- super(editor, preview, "Question "+ Layout.SHOW_EDITOR_AND_PREVIEW.getName() ,Layout.SHOW_EDITOR_AND_PREVIEW);
+ super(editor, preview, "Question " + Layout.SHOW_EDITOR_AND_PREVIEW.getName(), Layout.SHOW_EDITOR_AND_PREVIEW);
editor.putUserData(PARENT_SPLIT_EDITOR_KEY, this);
preview.putUserData(PARENT_SPLIT_EDITOR_KEY, this);
- if(preview instanceof MarkdownPreviewFileEditor){
- try {
- Method setMainEditor = preview.getClass().getMethod("setMainEditor", Editor.class);
- setMainEditor.invoke(preview,editor.getEditor());
- }catch (Throwable ignore){
- }
- }
}
@Nullable
diff --git a/src/main/java/com/shuzijun/leetcode/plugin/editor/ResourcesController.java b/src/main/java/com/shuzijun/leetcode/plugin/editor/ResourcesController.java
new file mode 100644
index 00000000..27bb2e9c
--- /dev/null
+++ b/src/main/java/com/shuzijun/leetcode/plugin/editor/ResourcesController.java
@@ -0,0 +1,48 @@
+package com.shuzijun.leetcode.plugin.editor;
+
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.util.io.FileUtilRt;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.http.*;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.io.FileResponses;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * @author shuzijun
+ */
+public class ResourcesController extends BaseController {
+
+ private static final Logger LOG = Logger.getInstance(ResourcesController.class);
+
+ private final String controllerPath = "resources";
+
+ @Override
+ public String getControllerPath() {
+ return controllerPath;
+ }
+
+ @Override
+ public FullHttpResponse get(@NotNull QueryStringDecoder urlDecoder, @NotNull FullHttpRequest request, @NotNull ChannelHandlerContext context) {
+ String resourceName = getResourceName(urlDecoder);
+ byte[] data;
+ try (InputStream inputStream = PreviewStaticServer.class.getResourceAsStream(resourceName)) {
+ if (inputStream == null) {
+ return new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.NOT_FOUND, Unpooled.EMPTY_BUFFER);
+ }
+ data = FileUtilRt.loadBytes(inputStream);
+ } catch (IOException e) {
+ LOG.warn(e);
+ return new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.INTERNAL_SERVER_ERROR, Unpooled.EMPTY_BUFFER);
+ }
+
+ FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.wrappedBuffer(data));
+ response.headers().set(HttpHeaderNames.CONTENT_TYPE, FileResponses.INSTANCE.getContentType(resourceName) + "; charset=utf-8");
+ response.headers().set(HttpHeaderNames.CACHE_CONTROL, "max-age=3600, private, must-revalidate");
+ response.headers().set(HttpHeaderNames.ETAG, Long.toString(LAST_MODIFIED));
+ return response;
+ }
+}
diff --git a/src/main/java/com/shuzijun/leetcode/plugin/editor/SplitFileEditor.java b/src/main/java/com/shuzijun/leetcode/plugin/editor/SplitFileEditor.java
new file mode 100644
index 00000000..30f1ed06
--- /dev/null
+++ b/src/main/java/com/shuzijun/leetcode/plugin/editor/SplitFileEditor.java
@@ -0,0 +1,451 @@
+package com.shuzijun.leetcode.plugin.editor;
+
+import com.intellij.codeHighlighting.BackgroundEditorHighlighter;
+import com.intellij.ide.structureView.StructureViewBuilder;
+import com.intellij.openapi.actionSystem.ActionGroup;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.ActionPlaces;
+import com.intellij.openapi.actionSystem.ActionToolbar;
+import com.intellij.openapi.actionSystem.impl.ActionToolbarImpl;
+import com.intellij.openapi.fileEditor.*;
+import com.intellij.openapi.util.Disposer;
+import com.intellij.openapi.util.Key;
+import com.intellij.openapi.util.Pair;
+import com.intellij.openapi.util.UserDataHolderBase;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.wm.IdeFocusManager;
+import com.intellij.ui.JBSplitter;
+import com.intellij.util.ui.JBEmptyBorder;
+import com.shuzijun.leetcode.plugin.model.PluginConstant;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import java.awt.*;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.HashMap;
+import java.util.Map;
+
+import static com.intellij.openapi.fileEditor.TextEditorWithPreview.DEFAULT_LAYOUT_FOR_FILE;
+
+/**
+ * @author shuzijun
+ */
+public abstract class SplitFileEditor extends UserDataHolderBase implements FileEditor {
+ public static final Key PARENT_SPLIT_KEY = Key.create(PluginConstant.PLUGIN_ID + "parentSplit");
+
+ private static final String MY_PROPORTION_KEY = PluginConstant.PLUGIN_ID + "SplitFileEditor.Proportion";
+
+ @NotNull
+ protected final E1 myMainEditor;
+ @NotNull
+ protected final E2 mySecondEditor;
+ @NotNull
+ private final JComponent myComponent;
+ @NotNull
+ private SplitFileEditor.SplitEditorLayout mySplitEditorLayout = SplitEditorLayout.SPLIT;
+
+ private boolean myVerticalSplitOption = true;
+ @NotNull
+ private final SplitFileEditor.MyListenersMultimap myListenersGenerator = new SplitFileEditor.MyListenersMultimap();
+
+ private SplitEditorToolbar myToolbarWrapper;
+ private JBSplitter mySplitter;
+
+ public SplitFileEditor(@NotNull E1 mainEditor, @NotNull E2 secondEditor) {
+ myMainEditor = mainEditor;
+ mySecondEditor = secondEditor;
+
+ adjustDefaultLayout(mainEditor);
+ myComponent = createComponent();
+
+ if (myMainEditor instanceof TextEditor) {
+ myMainEditor.putUserData(PARENT_SPLIT_KEY, this);
+ }
+ if (mySecondEditor instanceof TextEditor) {
+ mySecondEditor.putUserData(PARENT_SPLIT_KEY, this);
+ }
+
+ /* MarkdownApplicationSettings.SettingsChangedListener settingsChangedListener =
+ new MarkdownApplicationSettings.SettingsChangedListener() {
+ @Override
+ public void beforeSettingsChanged(@NotNull MarkdownApplicationSettings newSettings) {
+ SplitFileEditor.SplitEditorLayout oldSplitEditorLayout =
+ MarkdownApplicationSettings.getInstance().getMarkdownPreviewSettings().getSplitEditorLayout();
+
+ boolean oldVerticalSplitOption =
+ MarkdownApplicationSettings.getInstance().getMarkdownPreviewSettings().isVerticalSplit();
+
+ ApplicationManager.getApplication().invokeLater(() -> {
+ if (oldSplitEditorLayout == mySplitEditorLayout) {
+ triggerLayoutChange(newSettings.getMarkdownPreviewSettings().getSplitEditorLayout(), false);
+ }
+
+ if (oldVerticalSplitOption == myVerticalSplitOption) {
+ triggerSplitOrientationChange(newSettings.getMarkdownPreviewSettings().isVerticalSplit());
+ }
+ });
+ }
+ };
+
+ ApplicationManager.getApplication().getMessageBus().connect(this)
+ .subscribe(MarkdownApplicationSettings.SettingsChangedListener.TOPIC, settingsChangedListener);*/
+ }
+
+ private void adjustDefaultLayout(E1 editor) {
+ TextEditorWithPreview.Layout layout = getAndResetPredefinedLayoutForEditor(editor);
+ if (layout != null) {
+ switch (layout) {
+ case SHOW_EDITOR:
+ mySplitEditorLayout = SplitFileEditor.SplitEditorLayout.FIRST;
+ break;
+ case SHOW_PREVIEW:
+ mySplitEditorLayout = SplitFileEditor.SplitEditorLayout.SECOND;
+ break;
+ case SHOW_EDITOR_AND_PREVIEW:
+ mySplitEditorLayout = SplitFileEditor.SplitEditorLayout.SPLIT;
+ break;
+ }
+ }
+ }
+
+ //todo: Refactor Markdown editor and make it a subclass of TextEditorWithPreview.
+ // Move this method to TextEditorWithPreview.
+ @Nullable
+ private static TextEditorWithPreview.Layout getAndResetPredefinedLayoutForEditor(FileEditor editor) {
+ VirtualFile file = editor.getFile();
+ if (file != null) {
+ TextEditorWithPreview.Layout layout = file.getUserData(DEFAULT_LAYOUT_FOR_FILE);
+ if (layout != null) {
+ file.putUserData(DEFAULT_LAYOUT_FOR_FILE, null); //burn after reading
+ return layout;
+ }
+ }
+
+ return null;
+ }
+
+ private void triggerSplitOrientationChange(boolean isVerticalSplit) {
+ if (myVerticalSplitOption == isVerticalSplit) {
+ return;
+ }
+
+ myVerticalSplitOption = isVerticalSplit;
+
+ myToolbarWrapper.refresh();
+ mySplitter.setOrientation(!myVerticalSplitOption);
+ myComponent.repaint();
+ }
+
+ @NotNull
+ private JComponent createComponent() {
+ mySplitter =
+ new JBSplitter(!myVerticalSplitOption, 0.5f, 0.15f, 0.85f);
+ mySplitter.setSplitterProportionKey(MY_PROPORTION_KEY);
+ mySplitter.setFirstComponent(myMainEditor.getComponent());
+ mySplitter.setSecondComponent(mySecondEditor.getComponent());
+ mySplitter.setDividerWidth(3);
+
+ myToolbarWrapper = createMarkdownToolbarWrapper(mySplitter);
+
+ final JPanel result = new JPanel(new BorderLayout());
+ result.add(myToolbarWrapper, BorderLayout.NORTH);
+ result.add(mySplitter, BorderLayout.CENTER);
+ adjustEditorsVisibility();
+
+ return result;
+ }
+
+ @NotNull
+ private static SplitEditorToolbar createMarkdownToolbarWrapper(@NotNull JComponent targetComponentForActions) {
+ ActionToolbar leftToolbar = createToolbarFromGroupId("Markdown.Toolbar.Left");
+ leftToolbar.setTargetComponent(targetComponentForActions);
+ leftToolbar.setReservePlaceAutoPopupIcon(false);
+
+ ActionToolbar rightToolbar = createToolbarFromGroupId("Markdown.Toolbar.Right");
+ rightToolbar.setTargetComponent(targetComponentForActions);
+ rightToolbar.setReservePlaceAutoPopupIcon(false);
+
+ return new SplitEditorToolbar(leftToolbar, rightToolbar);
+ }
+
+ @NotNull
+ private static ActionToolbar createToolbarFromGroupId(@NotNull String groupId) {
+ final ActionManager actionManager = ActionManager.getInstance();
+
+ if (!actionManager.isGroup(groupId)) {
+ throw new IllegalStateException(groupId + " should have been a group");
+ }
+ final ActionGroup group = ((ActionGroup) actionManager.getAction(groupId));
+ final ActionToolbarImpl editorToolbar =
+ ((ActionToolbarImpl) actionManager.createActionToolbar(ActionPlaces.EDITOR_TOOLBAR, group, true));
+ editorToolbar.setBorder(new JBEmptyBorder(0, 2, 0, 2));
+
+ return editorToolbar;
+ }
+
+ public void triggerLayoutChange() {
+ final int oldValue = mySplitEditorLayout.ordinal();
+ final int N = SplitFileEditor.SplitEditorLayout.values().length;
+ final int newValue = (oldValue + N - 1) % N;
+
+ triggerLayoutChange(SplitFileEditor.SplitEditorLayout.values()[newValue], true);
+ }
+
+ public void triggerLayoutChange(@NotNull SplitFileEditor.SplitEditorLayout newLayout, boolean requestFocus) {
+ if (mySplitEditorLayout == newLayout) {
+ return;
+ }
+
+ mySplitEditorLayout = newLayout;
+ invalidateLayout(requestFocus);
+ }
+
+ @NotNull
+ public SplitFileEditor.SplitEditorLayout getCurrentEditorLayout() {
+ return mySplitEditorLayout;
+ }
+
+ private void invalidateLayout(boolean requestFocus) {
+ adjustEditorsVisibility();
+ myToolbarWrapper.refresh();
+ myComponent.repaint();
+
+ if (!requestFocus) return;
+
+ final JComponent focusComponent = getPreferredFocusedComponent();
+ if (focusComponent != null) {
+ IdeFocusManager.findInstanceByComponent(focusComponent).requestFocus(focusComponent, true);
+ }
+ }
+
+ private void adjustEditorsVisibility() {
+ myMainEditor.getComponent().setVisible(mySplitEditorLayout.showFirst);
+ mySecondEditor.getComponent().setVisible(mySplitEditorLayout.showSecond);
+ }
+
+ @NotNull
+ public E1 getMainEditor() {
+ return myMainEditor;
+ }
+
+ @NotNull
+ public E2 getSecondEditor() {
+ return mySecondEditor;
+ }
+
+ @NotNull
+ @Override
+ public JComponent getComponent() {
+ return myComponent;
+ }
+
+ @Nullable
+ @Override
+ public JComponent getPreferredFocusedComponent() {
+ if (mySplitEditorLayout.showFirst) return myMainEditor.getPreferredFocusedComponent();
+ if (mySplitEditorLayout.showSecond) return mySecondEditor.getPreferredFocusedComponent();
+ return null;
+ }
+
+ @NotNull
+ @Override
+ public FileEditorState getState(@NotNull FileEditorStateLevel level) {
+ return new SplitFileEditor.MyFileEditorState(mySplitEditorLayout.name(), myMainEditor.getState(level), mySecondEditor.getState(level));
+ }
+
+ @Override
+ public void setState(@NotNull FileEditorState state) {
+ if (state instanceof SplitFileEditor.MyFileEditorState) {
+ final SplitFileEditor.MyFileEditorState compositeState = (SplitFileEditor.MyFileEditorState) state;
+ if (compositeState.getFirstState() != null) {
+ myMainEditor.setState(compositeState.getFirstState());
+ }
+ if (compositeState.getSecondState() != null) {
+ mySecondEditor.setState(compositeState.getSecondState());
+ }
+ if (compositeState.getSplitLayout() != null) {
+ mySplitEditorLayout = SplitFileEditor.SplitEditorLayout.valueOf(compositeState.getSplitLayout());
+ invalidateLayout(true);
+ }
+ }
+ }
+
+ @Override
+ public boolean isModified() {
+ return myMainEditor.isModified() || mySecondEditor.isModified();
+ }
+
+ @Override
+ public boolean isValid() {
+ return myMainEditor.isValid() && mySecondEditor.isValid();
+ }
+
+ @Override
+ public void selectNotify() {
+ myMainEditor.selectNotify();
+ mySecondEditor.selectNotify();
+ }
+
+ @Override
+ public void deselectNotify() {
+ myMainEditor.deselectNotify();
+ mySecondEditor.deselectNotify();
+ }
+
+ @Override
+ public void addPropertyChangeListener(@NotNull PropertyChangeListener listener) {
+ myMainEditor.addPropertyChangeListener(listener);
+ mySecondEditor.addPropertyChangeListener(listener);
+
+ final SplitFileEditor.DoublingEventListenerDelegate delegate = myListenersGenerator.addListenerAndGetDelegate(listener);
+ myMainEditor.addPropertyChangeListener(delegate);
+ mySecondEditor.addPropertyChangeListener(delegate);
+ }
+
+ @Override
+ public void removePropertyChangeListener(@NotNull PropertyChangeListener listener) {
+ myMainEditor.removePropertyChangeListener(listener);
+ mySecondEditor.removePropertyChangeListener(listener);
+
+ final SplitFileEditor.DoublingEventListenerDelegate delegate = myListenersGenerator.removeListenerAndGetDelegate(listener);
+ if (delegate != null) {
+ myMainEditor.removePropertyChangeListener(delegate);
+ mySecondEditor.removePropertyChangeListener(delegate);
+ }
+ }
+
+ @Nullable
+ @Override
+ public BackgroundEditorHighlighter getBackgroundHighlighter() {
+ return myMainEditor.getBackgroundHighlighter();
+ }
+
+ @Nullable
+ @Override
+ public FileEditorLocation getCurrentLocation() {
+ return myMainEditor.getCurrentLocation();
+ }
+
+ @Nullable
+ @Override
+ public StructureViewBuilder getStructureViewBuilder() {
+ return myMainEditor.getStructureViewBuilder();
+ }
+
+ @Override
+ public void dispose() {
+ Disposer.dispose(myMainEditor);
+ Disposer.dispose(mySecondEditor);
+ }
+
+ public static class MyFileEditorState implements FileEditorState {
+ @Nullable
+ private final String mySplitLayout;
+ @Nullable
+ private final FileEditorState myFirstState;
+ @Nullable
+ private final FileEditorState mySecondState;
+
+ public MyFileEditorState(@Nullable String splitLayout, @Nullable FileEditorState firstState, @Nullable FileEditorState secondState) {
+ mySplitLayout = splitLayout;
+ myFirstState = firstState;
+ mySecondState = secondState;
+ }
+
+ @Nullable
+ public String getSplitLayout() {
+ return mySplitLayout;
+ }
+
+ @Nullable
+ public FileEditorState getFirstState() {
+ return myFirstState;
+ }
+
+ @Nullable
+ public FileEditorState getSecondState() {
+ return mySecondState;
+ }
+
+ @Override
+ public boolean canBeMergedWith(FileEditorState otherState, FileEditorStateLevel level) {
+ return otherState instanceof SplitFileEditor.MyFileEditorState
+ && (myFirstState == null || myFirstState.canBeMergedWith(((SplitFileEditor.MyFileEditorState) otherState).myFirstState, level))
+ && (mySecondState == null || mySecondState.canBeMergedWith(((SplitFileEditor.MyFileEditorState) otherState).mySecondState, level));
+ }
+ }
+
+ private class DoublingEventListenerDelegate implements PropertyChangeListener {
+ @NotNull
+ private final PropertyChangeListener myDelegate;
+
+ private DoublingEventListenerDelegate(@NotNull PropertyChangeListener delegate) {
+ myDelegate = delegate;
+ }
+
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ myDelegate.propertyChange(new PropertyChangeEvent(SplitFileEditor.this, evt.getPropertyName(), evt.getOldValue(), evt.getNewValue()));
+ }
+ }
+
+ private class MyListenersMultimap {
+ private final Map> myMap = new HashMap<>();
+
+ @NotNull
+ public SplitFileEditor.DoublingEventListenerDelegate addListenerAndGetDelegate(@NotNull PropertyChangeListener listener) {
+ if (!myMap.containsKey(listener)) {
+ myMap.put(listener, Pair.create(1, new SplitFileEditor.DoublingEventListenerDelegate(listener)));
+ } else {
+ final Pair oldPair = myMap.get(listener);
+ myMap.put(listener, Pair.create(oldPair.getFirst() + 1, oldPair.getSecond()));
+ }
+
+ return myMap.get(listener).getSecond();
+ }
+
+ @Nullable
+ public SplitFileEditor.DoublingEventListenerDelegate removeListenerAndGetDelegate(@NotNull PropertyChangeListener listener) {
+ final Pair oldPair = myMap.get(listener);
+ if (oldPair == null) {
+ return null;
+ }
+
+ if (oldPair.getFirst() == 1) {
+ myMap.remove(listener);
+ } else {
+ myMap.put(listener, Pair.create(oldPair.getFirst() - 1, oldPair.getSecond()));
+ }
+ return oldPair.getSecond();
+ }
+ }
+
+ public enum SplitEditorLayout {
+ FIRST(true, false, "editor only"),
+ SECOND(false, true, "preview only"),
+ SPLIT(true, true, "editor and preview");
+
+ public final boolean showFirst;
+ public final boolean showSecond;
+ public final String presentationName;
+
+ SplitEditorLayout(boolean showFirst, boolean showSecond, String presentationName) {
+ this.showFirst = showFirst;
+ this.showSecond = showSecond;
+ this.presentationName = presentationName;
+ }
+
+ public String getPresentationText() {
+ return StringUtil.capitalize(presentationName);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("Show %s", presentationName);
+ }
+ }
+}
+
diff --git a/src/main/java/com/shuzijun/leetcode/plugin/editor/SplitTextEditorProvider.java b/src/main/java/com/shuzijun/leetcode/plugin/editor/SplitTextEditorProvider.java
new file mode 100644
index 00000000..1e5a22fa
--- /dev/null
+++ b/src/main/java/com/shuzijun/leetcode/plugin/editor/SplitTextEditorProvider.java
@@ -0,0 +1,139 @@
+package com.shuzijun.leetcode.plugin.editor;
+
+import com.intellij.openapi.fileEditor.*;
+import com.intellij.openapi.project.DumbAware;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jdom.Attribute;
+import org.jdom.Element;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author shuzijun
+ */
+public abstract class SplitTextEditorProvider implements AsyncFileEditorProvider, DumbAware {
+
+ private static final String FIRST_EDITOR = "first_editor";
+ private static final String SECOND_EDITOR = "second_editor";
+ private static final String SPLIT_LAYOUT = "split_layout";
+
+ @NotNull
+ protected final FileEditorProvider myFirstProvider;
+ @NotNull
+ protected final FileEditorProvider mySecondProvider;
+
+ @NotNull
+ private final String myEditorTypeId;
+
+ public SplitTextEditorProvider(@NotNull FileEditorProvider firstProvider, @NotNull FileEditorProvider secondProvider) {
+ myFirstProvider = firstProvider;
+ mySecondProvider = secondProvider;
+
+ myEditorTypeId = "split-provider[" + myFirstProvider.getEditorTypeId() + ";" + mySecondProvider.getEditorTypeId() + "]";
+ }
+
+ @Override
+ public boolean accept(@NotNull Project project, @NotNull VirtualFile file) {
+ return myFirstProvider.accept(project, file) && mySecondProvider.accept(project, file);
+ }
+
+ @NotNull
+ @Override
+ public FileEditor createEditor(@NotNull Project project, @NotNull VirtualFile file) {
+ return createEditorAsync(project, file).build();
+ }
+
+ @NotNull
+ @Override
+ public String getEditorTypeId() {
+ return myEditorTypeId;
+ }
+
+ @NotNull
+ @Override
+ public AsyncFileEditorProvider.Builder createEditorAsync(@NotNull final Project project, @NotNull final VirtualFile file) {
+ final Builder firstBuilder = getBuilderFromEditorProvider(myFirstProvider, project, file);
+ final Builder secondBuilder = getBuilderFromEditorProvider(mySecondProvider, project, file);
+
+ return new Builder() {
+ @Override
+ public FileEditor build() {
+ return createSplitEditor(firstBuilder.build(), secondBuilder.build());
+ }
+ };
+ }
+
+ @NotNull
+ @Override
+ public FileEditorState readState(@NotNull Element sourceElement, @NotNull Project project, @NotNull VirtualFile file) {
+ Element child = sourceElement.getChild(FIRST_EDITOR);
+ FileEditorState firstState = null;
+ if (child != null) {
+ firstState = myFirstProvider.readState(child, project, file);
+ }
+ child = sourceElement.getChild(SECOND_EDITOR);
+ FileEditorState secondState = null;
+ if (child != null) {
+ secondState = mySecondProvider.readState(child, project, file);
+ }
+
+ final Attribute attribute = sourceElement.getAttribute(SPLIT_LAYOUT);
+
+ final String layoutName;
+ if (attribute != null) {
+ layoutName = attribute.getValue();
+ } else {
+ layoutName = null;
+ }
+
+ return new SplitFileEditor.MyFileEditorState(layoutName, firstState, secondState);
+ }
+
+ @Override
+ public void writeState(@NotNull FileEditorState state, @NotNull Project project, @NotNull Element targetElement) {
+ if (!(state instanceof SplitFileEditor.MyFileEditorState)) {
+ return;
+ }
+ final SplitFileEditor.MyFileEditorState compositeState = (SplitFileEditor.MyFileEditorState) state;
+
+ Element child = new Element(FIRST_EDITOR);
+ if (compositeState.getFirstState() != null) {
+ myFirstProvider.writeState(compositeState.getFirstState(), project, child);
+ targetElement.addContent(child);
+ }
+
+ child = new Element(SECOND_EDITOR);
+ if (compositeState.getSecondState() != null) {
+ mySecondProvider.writeState(compositeState.getSecondState(), project, child);
+ targetElement.addContent(child);
+ }
+
+ if (compositeState.getSplitLayout() != null) {
+ targetElement.setAttribute(SPLIT_LAYOUT, compositeState.getSplitLayout());
+ }
+ }
+
+ protected abstract FileEditor createSplitEditor(@NotNull FileEditor firstEditor, @NotNull FileEditor secondEditor);
+
+ @NotNull
+ @Override
+ public FileEditorPolicy getPolicy() {
+ return FileEditorPolicy.HIDE_DEFAULT_EDITOR;
+ }
+
+ @NotNull
+ public static Builder getBuilderFromEditorProvider(@NotNull final FileEditorProvider provider,
+ @NotNull final Project project,
+ @NotNull final VirtualFile file) {
+ if (provider instanceof AsyncFileEditorProvider) {
+ return ((AsyncFileEditorProvider) provider).createEditorAsync(project, file);
+ } else {
+ return new Builder() {
+ @Override
+ public FileEditor build() {
+ return provider.createEditor(project, file);
+ }
+ };
+ }
+ }
+}
diff --git a/src/main/java/com/shuzijun/leetcode/plugin/manager/ArticleManager.java b/src/main/java/com/shuzijun/leetcode/plugin/manager/ArticleManager.java
index 34dbf9b5..efe2e3d2 100644
--- a/src/main/java/com/shuzijun/leetcode/plugin/manager/ArticleManager.java
+++ b/src/main/java/com/shuzijun/leetcode/plugin/manager/ArticleManager.java
@@ -1,55 +1,45 @@
package com.shuzijun.leetcode.plugin.manager;
-import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.intellij.openapi.project.Project;
-import com.intellij.ui.JBColor;
import com.shuzijun.leetcode.plugin.model.Constant;
+import com.shuzijun.leetcode.plugin.model.PluginConstant;
import com.shuzijun.leetcode.plugin.model.Question;
import com.shuzijun.leetcode.plugin.model.Solution;
import com.shuzijun.leetcode.plugin.setting.PersistentConfig;
import com.shuzijun.leetcode.plugin.utils.*;
+import com.shuzijun.leetcode.plugin.utils.doc.CleanMarkdown;
import org.apache.commons.lang.StringUtils;
-import org.jsoup.Jsoup;
-import org.scilab.forge.jlatexmath.TeXConstants;
-import org.scilab.forge.jlatexmath.TeXFormula;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
/**
* @author shuzijun
*/
public class ArticleManager {
- private static final Pattern latexPattern = Pattern.compile("\\$+[\\s|\\S]*?\\$+");
- private static final Pattern imageListPattern = Pattern.compile("<(!\\[.*]\\(.*\\),*)+>?");
- private static final Pattern imagePattern = Pattern.compile("?");
- private static final Pattern linkPattern = Pattern.compile("]\\(\\.\\.[\\w|/]*?\\.\\w+\\)?");
- private static final Pattern gifPattern = Pattern.compile("!\\[.*?]\\(.*?\\.gif\\)?");
- private static final Pattern playgroundPattern = Pattern.compile("?");
- private static final Pattern imageGroupPattern = Pattern.compile("!\\?!\\.\\..*\\?!?");
-
public static void openArticle(Question question, Project project) {
- String filePath = PersistentConfig.getInstance().getTempFilePath() + Constant.DOC_SOLUTION + question.getArticleSlug() + ".md";
+ String filePath = PersistentConfig.getInstance().getTempFilePath() + Constant.DOC_SOLUTION + question.getArticleSlug() + "." + PluginConstant.LEETCODE_EDITOR_VIEW;
File file = new File(filePath);
+ String host = "";
if (!file.exists()) {
String article;
if (URLUtils.isCn()) {
article = getCnArticle(question, project);
+ host = URLUtils.getLeetcodeProblems() + question.getTitleSlug() + "/solution/" + question.getArticleSlug() + "/";
} else {
article = getEnArticle(question, project);
+ host = URLUtils.getLeetcodeProblems() + question.getTitleSlug() + "/solution/";
}
if (StringUtils.isBlank(article)) {
return;
}
- article = formatMarkdown(article, project);
+ article = formatMarkdown(article, project,host);
FileUtils.saveFile(file, article);
}
@@ -112,84 +102,8 @@ private static String getCnArticle(Question question, Project project) {
}
- public static String formatMarkdown(String content, Project project) {
- String article = content.replaceAll("\\{:\\w+=\"?.*\"?}?", "");
- Matcher latexMatcher = latexPattern.matcher(content);
- while (latexMatcher.find()) {
- String group = latexMatcher.group();
- if (group.contains("\\")) {
- String fileName = "p_" + group.replaceAll("\\$+| |/|>|<|\\(|\\)|\\s|\\[|]", "_").replace("\\", "") + ".png";
- String filePath = PersistentConfig.getInstance().getTempFilePath() + Constant.DOC_SOLUTION + fileName;
- File file = new File(filePath);
- if (!file.exists()) {
- if (!file.getParentFile().exists()) {
- file.getParentFile().mkdirs();
- }
- try {
- TeXFormula formula = new TeXFormula(group.replaceAll("\\$+|\\s", " "));
- formula.createPNG(TeXConstants.STYLE_DISPLAY, 14, filePath, null, JBColor.BLACK);
- } catch (Exception e) {
- LogUtils.LOG.error("TeXFormula error", e);
- }
- }
- article = article.replace(group, "![" + group.replaceAll("\\$+|\\s", "").replaceAll("([\\[\\]])", "\\\\$1") + " ](./" + fileName + ") ");
- } else {
- article = article.replace(group, group.replaceAll("\\$+", "*"));
- }
- }
- Matcher imageListMatcher = imageListPattern.matcher(content);
- while (imageListMatcher.find()) {
- article = article.replace(imageListMatcher.group(), imageListMatcher.group().replaceAll("[<>,]"," "));
- }
-
- Matcher linkMatcher = linkPattern.matcher(content);
- while (linkMatcher.find()) {
- article = article.replace(linkMatcher.group(), linkMatcher.group().replace("..", URLUtils.getLeetcodeProblems()));
- }
- Matcher imageMatcher = imagePattern.matcher(content);
- while (imageMatcher.find()) {
- article = article.replace(imageMatcher.group(), imageMatcher.group().replace("..", URLUtils.getLeetcodeProblems()));
- }
- Matcher gifMatcher = gifPattern.matcher(content);
- while (gifMatcher.find()) {
- article = article.replace(gifMatcher.group(), gifMatcher.group().replace("!", " "));
- }
-
- Matcher playgroundMatcher = playgroundPattern.matcher(content);
- while (playgroundMatcher.find()) {
- String group = playgroundMatcher.group();
- String name = Jsoup.parse(group).select("iframe").attr("name");
- HttpRequest playgroundHttpRequest = HttpRequest.post(URLUtils.getLeetcodeGraphql(), "application/json");
- playgroundHttpRequest.setBody("{\"operationName\":\"fetchPlayground\",\"variables\":{},\"query\":\"query fetchPlayground {\\n playground(uuid: \\\"" + name + "\\\") {\\n testcaseInput\\n name\\n isUserOwner\\n" +
- " isLive\\n showRunCode\\n showOpenInPlayground\\n selectedLangSlug\\n isShared\\n __typename\\n }\\n allPlaygroundCodes(uuid: \\\"" + name + "\\\") {\\n code\\n langSlug\\n __typename\\n }\\n}\\n\"}");
- playgroundHttpRequest.addHeader("Accept", "application/json");
- HttpResponse playgroundResponse = HttpRequestUtils.executePost(playgroundHttpRequest);
- if (playgroundResponse.getStatusCode() == 200) {
- String playgroundCode = JSONObject.parseObject(playgroundResponse.getBody()).getJSONObject("data").getJSONArray("allPlaygroundCodes").getJSONObject(0).getString("code");
- article = article.replace(group, "```\n" + playgroundCode + "\n```");
- }
- }
-
- Matcher imageGroupMatcher = imageGroupPattern.matcher(content);
- while (imageGroupMatcher.find()) {
- String group = imageGroupMatcher.group();
- HttpRequest imageGroupHttpRequest = HttpRequest.get(URLUtils.getLeetcodeProblems()+group.replace("!?!..","").replaceAll(":\\d+,\\d+!\\?!",""));
- HttpResponse imageGroupResponse = HttpRequestUtils.executeGet(imageGroupHttpRequest);
- if (imageGroupResponse.getStatusCode() == 200) {
- JSONArray jsonArray = JSON.parseObject(imageGroupResponse.getBody()).getJSONArray("timeline");
- StringBuffer imgs = new StringBuffer();
- for (int i = 0; i < jsonArray.size(); i++) {
- JSONObject jsonObject = jsonArray.getJSONObject(i);
- imgs.append(".append(URLUtils.getLeetcodeProblems()).append(jsonObject.getString("image").replace("..","")).append(" ) ");
- }
- article = article.replace(group, imgs.toString());
- }
-
- }
-
-
-
- return article;
+ public static String formatMarkdown(String content, Project project,String host) {
+ return CleanMarkdown.cleanMarkdown(content, host);
}
public static List getSolutionList(Question question, Project project) {
diff --git a/src/main/java/com/shuzijun/leetcode/plugin/manager/CodeManager.java b/src/main/java/com/shuzijun/leetcode/plugin/manager/CodeManager.java
index 68a81436..fd650871 100644
--- a/src/main/java/com/shuzijun/leetcode/plugin/manager/CodeManager.java
+++ b/src/main/java/com/shuzijun/leetcode/plugin/manager/CodeManager.java
@@ -46,8 +46,7 @@ public static void openCode(Question question, Project project) {
if (file.exists()) {
FileUtils.openFileEditorAndSaveState(file,project,question,fillPath,true);
} else {
-
- if (getQuestion(question, codeTypeEnum, project)) {
+ if (config.getQuestionEditor() || getQuestion(question, codeTypeEnum, project)) {
question.setContent(CommentUtils.createComment(question.getContent(), codeTypeEnum,config));
FileUtils.saveFile(file, VelocityUtils.convert(config.getCustomTemplate(), question));
FileUtils.openFileEditorAndSaveState(file,project,question,fillPath,true);
@@ -298,21 +297,24 @@ private static boolean fillQuestion(Question question, Project project) {
private static String getContent(JSONObject jsonObject) {
StringBuffer sb = new StringBuffer();
sb.append(jsonObject.getString(URLUtils.getDescContent()));
- JSONArray topicTagsArray = jsonObject.getJSONArray("topicTags");
- if (topicTagsArray != null && !topicTagsArray.isEmpty()) {
- sb.append("Related Topics
");
- for (int i = 0; i < topicTagsArray.size(); i++) {
- JSONObject tag = topicTagsArray.getJSONObject(i);
- sb.append("
");
- if (StringUtils.isBlank(tag.getString("translatedName"))) {
- sb.append(tag.getString("name"));
- } else {
- sb.append(tag.getString("translatedName"));
+ Config config = PersistentConfig.getInstance().getConfig();
+ if(config.getShowTopics()) {
+ JSONArray topicTagsArray = jsonObject.getJSONArray("topicTags");
+ if (topicTagsArray != null && !topicTagsArray.isEmpty()) {
+ sb.append("Related Topics
");
+ for (int i = 0; i < topicTagsArray.size(); i++) {
+ JSONObject tag = topicTagsArray.getJSONObject(i);
+ sb.append("
");
+ if (StringUtils.isBlank(tag.getString("translatedName"))) {
+ sb.append(tag.getString("name"));
+ } else {
+ sb.append(tag.getString("translatedName"));
+ }
+ sb.append("");
}
- sb.append("");
+ sb.append(" ");
+ sb.append("
");
}
- sb.append(" ");
- sb.append("
");
}
sb.append("\uD83D\uDC4D "+jsonObject.getInteger("likes")+"\uD83D\uDC4E "+jsonObject.getInteger("dislikes")+"");
return sb.toString();
diff --git a/src/main/java/com/shuzijun/leetcode/plugin/model/Config.java b/src/main/java/com/shuzijun/leetcode/plugin/model/Config.java
index bb69e198..5c818a60 100644
--- a/src/main/java/com/shuzijun/leetcode/plugin/model/Config.java
+++ b/src/main/java/com/shuzijun/leetcode/plugin/model/Config.java
@@ -3,10 +3,8 @@
import com.intellij.util.xmlb.annotations.Transient;
import java.awt.*;
-import java.util.ArrayList;
-import java.util.HashMap;
+import java.util.*;
import java.util.List;
-import java.util.Map;
/**
* @author shuzijun
@@ -105,6 +103,16 @@ public class Config {
*/
private Boolean htmlContent = false;
+ /**
+ * showTopics
+ */
+ private Boolean showTopics = true;
+
+ /**
+ * showToolIcon
+ */
+ private Boolean showToolIcon = true;
+
private List favoriteList;
public String getId() {
@@ -347,33 +355,53 @@ public void setHtmlContent(Boolean htmlContent) {
this.htmlContent = htmlContent;
}
+ public Boolean getShowTopics() {
+ return showTopics;
+ }
+
+ public void setShowTopics(Boolean showTopics) {
+ this.showTopics = showTopics;
+ }
+
+ public Boolean getShowToolIcon() {
+ return showToolIcon;
+ }
+
+ public void setShowToolIcon(Boolean showToolIcon) {
+ this.showToolIcon = showToolIcon;
+ }
+
public boolean isModified(Config config){
if(config ==null){
return false;
}
- if (version != null ? !version.equals(config.version) : config.version != null) return false;
- if (loginName != null ? !loginName.equals(config.loginName) : config.loginName != null) return false;
- if (filePath != null ? !filePath.equals(config.filePath) : config.filePath != null) return false;
- if (codeType != null ? !codeType.equals(config.codeType) : config.codeType != null) return false;
- if (url != null ? !url.equals(config.url) : config.url != null) return false;
- if (update != null ? !update.equals(config.update) : config.update != null) return false;
- if (proxy != null ? !proxy.equals(config.proxy) : config.proxy != null) return false;
- if (customCode != null ? !customCode.equals(config.customCode) : config.customCode != null) return false;
- if (englishContent != null ? !englishContent.equals(config.englishContent) : config.englishContent != null)
+ if (!Objects.equals(version, config.version)) return false;
+ if (!Objects.equals(loginName, config.loginName)) return false;
+ if (!Objects.equals(filePath, config.filePath)) return false;
+ if (!Objects.equals(codeType, config.codeType)) return false;
+ if (!Objects.equals(url, config.url)) return false;
+ if (!Objects.equals(update, config.update)) return false;
+ if (!Objects.equals(proxy, config.proxy)) return false;
+ if (!Objects.equals(customCode, config.customCode)) return false;
+ if (!Objects.equals(englishContent, config.englishContent))
+ return false;
+ if (!Objects.equals(customFileName, config.customFileName))
+ return false;
+ if (!Objects.equals(customTemplate, config.customTemplate))
return false;
- if (customFileName != null ? !customFileName.equals(config.customFileName) : config.customFileName != null)
+ if (!Objects.equals(jcef, config.jcef))
return false;
- if (customTemplate != null ? !customTemplate.equals(config.customTemplate) : config.customTemplate != null)
+ if (!Objects.equals(questionEditor, config.questionEditor))
return false;
- if (jcef != null ? !jcef.equals(config.jcef) : config.jcef != null)
+ if (!Objects.equals(multilineComment, config.multilineComment))
return false;
- if (questionEditor != null ? !questionEditor.equals(config.questionEditor) : config.questionEditor != null)
+ if (!Objects.equals(htmlContent, config.htmlContent))
return false;
- if (multilineComment != null ? !multilineComment.equals(config.multilineComment) : config.multilineComment != null)
+ if (!Objects.equals(showTopics, config.showTopics))
return false;
- if (htmlContent != null ? !htmlContent.equals(config.htmlContent) : config.htmlContent != null)
+ if (!Objects.equals(showToolIcon, config.showToolIcon))
return false;
- return levelColour != null ? levelColour.equals(config.levelColour) : config.levelColour == null;
+ return Objects.equals(levelColour, config.levelColour);
}
diff --git a/src/main/java/com/shuzijun/leetcode/plugin/model/PluginConstant.java b/src/main/java/com/shuzijun/leetcode/plugin/model/PluginConstant.java
index 40375f60..68dd51a5 100644
--- a/src/main/java/com/shuzijun/leetcode/plugin/model/PluginConstant.java
+++ b/src/main/java/com/shuzijun/leetcode/plugin/model/PluginConstant.java
@@ -56,4 +56,7 @@ public class PluginConstant {
public static final String LEETCODE_EDITOR_GROUP = ACTION_PREFIX + ".editor.group";
public static final String LEETCODE_EDITOR_TIMER_STATUS_BAR_ID = PLUGIN_ID + "-TimerStatusBar";
+
+ public static final String LEETCODE_EDITOR_VIEW = "lcv";
+
}
diff --git a/src/main/java/com/shuzijun/leetcode/plugin/setting/SettingUI.form b/src/main/java/com/shuzijun/leetcode/plugin/setting/SettingUI.form
index 4d342bcb..c991bf55 100644
--- a/src/main/java/com/shuzijun/leetcode/plugin/setting/SettingUI.form
+++ b/src/main/java/com/shuzijun/leetcode/plugin/setting/SettingUI.form
@@ -13,7 +13,7 @@
-
+
@@ -157,6 +157,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/java/com/shuzijun/leetcode/plugin/setting/SettingUI.java b/src/main/java/com/shuzijun/leetcode/plugin/setting/SettingUI.java
index ab471f45..df58a53e 100644
--- a/src/main/java/com/shuzijun/leetcode/plugin/setting/SettingUI.java
+++ b/src/main/java/com/shuzijun/leetcode/plugin/setting/SettingUI.java
@@ -59,6 +59,8 @@ public class SettingUI {
private JCheckBox jcefCheckBox;
private JCheckBox multilineCheckBox;
private JCheckBox htmlContentCheckBox;
+ private JCheckBox showTopicsCheckBox;
+ private JCheckBox showToolIconCheckBox;
private Editor fileNameEditor = null;
@@ -190,6 +192,8 @@ private void loadSetting() {
questionEditorCheckBox.setSelected(config.getQuestionEditor());
multilineCheckBox.setSelected(config.getMultilineComment());
htmlContentCheckBox.setSelected(config.getHtmlContent());
+ showTopicsCheckBox.setSelected(config.getShowTopics());
+ showToolIconCheckBox.setSelected(config.getShowToolIcon());
} else {
Color[] colors = new Config().getFormatLevelColour();
easyLabel.setForeground(colors[0]);
@@ -260,6 +264,8 @@ public void process(Config config) {
config.setQuestionEditor(questionEditorCheckBox.isSelected());
config.setMultilineComment(multilineCheckBox.isSelected());
config.setHtmlContent(htmlContentCheckBox.isSelected());
+ config.setShowTopics(showTopicsCheckBox.isSelected());
+ config.setShowToolIcon(showToolIconCheckBox.isSelected());
}
diff --git a/src/main/java/com/shuzijun/leetcode/plugin/utils/FileUtils.java b/src/main/java/com/shuzijun/leetcode/plugin/utils/FileUtils.java
index 5e0203d8..e1003074 100644
--- a/src/main/java/com/shuzijun/leetcode/plugin/utils/FileUtils.java
+++ b/src/main/java/com/shuzijun/leetcode/plugin/utils/FileUtils.java
@@ -250,4 +250,12 @@ public static void saveEditDocument(VirtualFile file){
}
}
+ public static String separator() {
+ if (File.separator.equals("\\")) {
+ return "/";
+ } else {
+ return "";
+ }
+ }
+
}
diff --git a/src/main/java/com/shuzijun/leetcode/plugin/utils/doc/CleanExtensions.java b/src/main/java/com/shuzijun/leetcode/plugin/utils/doc/CleanExtensions.java
new file mode 100644
index 00000000..e7aeb1a2
--- /dev/null
+++ b/src/main/java/com/shuzijun/leetcode/plugin/utils/doc/CleanExtensions.java
@@ -0,0 +1,35 @@
+package com.shuzijun.leetcode.plugin.utils.doc;
+
+import com.vladsch.flexmark.formatter.Formatter;
+import com.vladsch.flexmark.parser.Parser;
+import com.vladsch.flexmark.util.data.MutableDataHolder;
+
+/**
+ * @author shuzijun
+ */
+public class CleanExtensions implements Formatter.FormatterExtension, Parser.ParserExtension {
+
+ public static CleanExtensions create() {
+ return new CleanExtensions();
+ }
+
+ @Override
+ public void rendererOptions(MutableDataHolder mutableDataHolder) {
+
+ }
+
+ @Override
+ public void extend(Formatter.Builder builder) {
+ builder.nodeFormatterFactory(new CleanNodeFormatter.Factory());
+ }
+
+ @Override
+ public void parserOptions(MutableDataHolder options) {
+
+ }
+
+ @Override
+ public void extend(Parser.Builder parserBuilder) {
+ parserBuilder.customDelimiterProcessor(new InlineLaTexProcessor());
+ }
+}
diff --git a/src/main/java/com/shuzijun/leetcode/plugin/utils/doc/CleanMarkdown.java b/src/main/java/com/shuzijun/leetcode/plugin/utils/doc/CleanMarkdown.java
new file mode 100644
index 00000000..b87e6ee9
--- /dev/null
+++ b/src/main/java/com/shuzijun/leetcode/plugin/utils/doc/CleanMarkdown.java
@@ -0,0 +1,34 @@
+package com.shuzijun.leetcode.plugin.utils.doc;
+
+import com.vladsch.flexmark.ext.attributes.AttributesExtension;
+import com.vladsch.flexmark.formatter.Formatter;
+import com.vladsch.flexmark.parser.Parser;
+import com.vladsch.flexmark.util.ast.Node;
+import com.vladsch.flexmark.util.data.DataHolder;
+import com.vladsch.flexmark.util.data.MutableDataSet;
+
+import java.util.Arrays;
+
+/**
+ * @author shuzijun
+ */
+public class CleanMarkdown {
+ final private static DataHolder OPTIONS = new MutableDataSet()
+ .set(Parser.EXTENSIONS, Arrays.asList(CleanExtensions.create(), AttributesExtension.create()))
+ .set(Parser.LISTS_AUTO_LOOSE, false)
+ .toImmutable();
+
+ static final Parser PARSER = Parser.builder(OPTIONS).build();
+ static final Formatter RENDERER = Formatter.builder(OPTIONS).build();
+
+ public static String cleanMarkdown(String markdown, String host) {
+ try {
+ CleanNodeFormatter.getThreadDataHolder().set(CleanNodeFormatter.FORMAT_HOST, host);
+ Node document = PARSER.parse(markdown);
+ return RENDERER.render(document);
+ } finally {
+ CleanNodeFormatter.removeThreadDataHolder();
+ }
+ }
+
+}
diff --git a/src/main/java/com/shuzijun/leetcode/plugin/utils/doc/CleanNodeFormatter.java b/src/main/java/com/shuzijun/leetcode/plugin/utils/doc/CleanNodeFormatter.java
new file mode 100644
index 00000000..f7492b22
--- /dev/null
+++ b/src/main/java/com/shuzijun/leetcode/plugin/utils/doc/CleanNodeFormatter.java
@@ -0,0 +1,165 @@
+package com.shuzijun.leetcode.plugin.utils.doc;
+
+import com.intellij.util.Url;
+import com.intellij.util.Urls;
+import com.intellij.util.io.URLUtil;
+import com.vladsch.flexmark.ast.FencedCodeBlock;
+import com.vladsch.flexmark.ast.HtmlBlock;
+import com.vladsch.flexmark.ast.HtmlInline;
+import com.vladsch.flexmark.ast.Image;
+import com.vladsch.flexmark.ext.attributes.AttributesNode;
+import com.vladsch.flexmark.formatter.*;
+import com.vladsch.flexmark.util.data.DataHolder;
+import com.vladsch.flexmark.util.data.DataKey;
+import com.vladsch.flexmark.util.data.MutableDataHolder;
+import com.vladsch.flexmark.util.data.MutableDataSet;
+import com.vladsch.flexmark.util.sequence.BasedSequence;
+import org.apache.commons.lang3.StringUtils;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+import org.jsoup.parser.Parser;
+import org.jsoup.select.Elements;
+
+import java.io.StringReader;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author shuzijun
+ */
+public class CleanNodeFormatter implements NodeFormatter {
+
+ public static final DataKey FORMAT_HOST = new DataKey<>("FORMAT_HOST", "");
+
+ private static final ThreadLocal threadOptions = ThreadLocal.withInitial(() -> new MutableDataSet());
+
+ public static MutableDataHolder getThreadDataHolder() {
+ return threadOptions.get();
+ }
+
+ public static void removeThreadDataHolder() {
+ threadOptions.remove();
+ }
+
+ @Override
+ public @Nullable Set> getNodeFormattingHandlers() {
+ return new HashSet<>(Arrays.asList(
+ new NodeFormattingHandler<>(FencedCodeBlock.class, CleanNodeFormatter.this::render),
+ new NodeFormattingHandler<>(HtmlBlock.class, CleanNodeFormatter.this::render),
+ new NodeFormattingHandler<>(HtmlInline.class, CleanNodeFormatter.this::render),
+ new NodeFormattingHandler<>(AttributesNode.class, CleanNodeFormatter.this::render),
+ new NodeFormattingHandler<>(InlineLaTexNode.class, CleanNodeFormatter.this::render),
+ new NodeFormattingHandler<>(Image.class, CleanNodeFormatter.this::render)
+ ));
+ }
+
+
+ private void render(@NotNull InlineLaTexNode texNode, @NotNull NodeFormatterContext nodeFormatterContext, @NotNull MarkdownWriter markdown) {
+ if (texNode.getText().startsWith(texNode.getOpeningDelimiter())) {
+ markdown.append(texNode.getText());
+ } else {
+ markdown.append(texNode.getOpeningDelimiter()).append(texNode.getText()).append(texNode.getClosingDelimiter());
+ }
+ }
+
+ private void render(@NotNull AttributesNode attributesNode, @NotNull NodeFormatterContext nodeFormatterContext, @NotNull MarkdownWriter lineInfos) {
+ }
+
+ private void render(FencedCodeBlock node, NodeFormatterContext context, MarkdownWriter markdown) {
+ BasedSequence[] basedSequences = node.getInfo().split(" ");
+ if (basedSequences.length > 1 && basedSequences[1].startsWith("[")) {
+ node.setInfo(basedSequences[0]);
+ markdown.blankLine();
+ markdown.append("* ");
+ for (int i = 1; i < basedSequences.length; i++) {
+ markdown.append(basedSequences[i].toString());
+ }
+ } else {
+ markdown.blankLine();
+ markdown.append("* ").append(node.getInfo().toString());
+ }
+ context.delegateRender();
+ }
+
+ private void render(@NotNull HtmlBlock htmlBlock, @NotNull NodeFormatterContext context, @NotNull MarkdownWriter lineInfos) {
+ htmlBlock.setChars(formatHtml(htmlBlock.getChars()));
+ context.delegateRender();
+ }
+
+ private void render(HtmlInline node, NodeFormatterContext context, MarkdownWriter markdown) {
+ if (node.getChars().startsWith("");
+ } else {
+ context.delegateRender();
+ }
+
+ }
+
+ private void render(Image node, NodeFormatterContext context, MarkdownWriter markdown) {
+ String url = formatUrl(node.getPageRef().toString());
+ node.setPageRef(BasedSequence.of(url));
+ context.delegateRender();
+ }
+
+ @Override
+ public @Nullable Set> getNodeClasses() {
+ return null;
+ }
+
+ public static class Factory implements NodeFormatterFactory {
+ public Factory() {
+ }
+
+ @NotNull
+ public NodeFormatter create(@NotNull DataHolder options) {
+ return new CleanNodeFormatter();
+ }
+ }
+
+ private BasedSequence formatHtml(BasedSequence Html) {
+ try {
+ Document document = Parser.xmlParser().parseInput(new StringReader(Html.toString()), "");
+ Elements elements = document.children();
+ for (Element element : elements) {
+ recursionElement(element);
+ }
+ return BasedSequence.of(document.html());
+ } catch (Exception e) {
+ return Html;
+ }
+ }
+
+ private void recursionElement(Element element) {
+ if ("img".equals(element.tagName())) {
+ String src = element.attr("src");
+ if (StringUtils.isNotBlank(src)) {
+ element.attr("src", formatUrl(src));
+ }
+ } else {
+ Elements elements = element.children();
+ for (Element element1 : elements) {
+ recursionElement(element1);
+ }
+ }
+ }
+
+ private String formatUrl(String url) {
+ if (!url.startsWith("http://") && !url.startsWith("https://")) {
+ String host = FORMAT_HOST.get(threadOptions.get());
+ if (url.startsWith("/")) {
+ Url urlParse = Urls.parseEncoded(host);
+ url = urlParse.getScheme() + URLUtil.SCHEME_SEPARATOR + urlParse.getAuthority() + url;
+ } else {
+ url = host + (host.endsWith("/") ? "" : "/") + url;
+ }
+ }
+ url = url.replace("\\(", "(");
+ url = url.replace("\\)", ")");
+ return url;
+ }
+}
diff --git a/src/main/java/com/shuzijun/leetcode/plugin/utils/doc/InlineLaTexNode.java b/src/main/java/com/shuzijun/leetcode/plugin/utils/doc/InlineLaTexNode.java
new file mode 100644
index 00000000..c99702e7
--- /dev/null
+++ b/src/main/java/com/shuzijun/leetcode/plugin/utils/doc/InlineLaTexNode.java
@@ -0,0 +1,37 @@
+package com.shuzijun.leetcode.plugin.utils.doc;
+
+import com.vladsch.flexmark.ast.DelimitedNodeImpl;
+import com.vladsch.flexmark.parser.core.delimiter.Delimiter;
+
+/**
+ * @author shuzijun
+ */
+public class InlineLaTexNode extends DelimitedNodeImpl {
+
+ public static final String HTML_TEX = "LaTex";
+
+ public static final String TOKEN_OPEN = "$";
+ public static final String TOKEN_CLOSE = "$";
+
+ private final String mOpener;
+ private final String mCloser;
+
+ public InlineLaTexNode(final Delimiter opener, final Delimiter closer) {
+ mOpener = getDelimiter(opener);
+ mCloser = getDelimiter(closer);
+ }
+
+ public String getOpeningDelimiter() {
+ return mOpener;
+ }
+
+ public String getClosingDelimiter() {
+ return mCloser;
+ }
+
+ private String getDelimiter(final Delimiter delimiter) {
+ return delimiter.getInput().subSequence(
+ delimiter.getStartIndex(), delimiter.getEndIndex()
+ ).toString();
+ }
+}
diff --git a/src/main/java/com/shuzijun/leetcode/plugin/utils/doc/InlineLaTexProcessor.java b/src/main/java/com/shuzijun/leetcode/plugin/utils/doc/InlineLaTexProcessor.java
new file mode 100644
index 00000000..824f9542
--- /dev/null
+++ b/src/main/java/com/shuzijun/leetcode/plugin/utils/doc/InlineLaTexProcessor.java
@@ -0,0 +1,76 @@
+package com.shuzijun.leetcode.plugin.utils.doc;
+
+import com.vladsch.flexmark.parser.InlineParser;
+import com.vladsch.flexmark.parser.core.delimiter.Delimiter;
+import com.vladsch.flexmark.parser.delimiter.DelimiterProcessor;
+import com.vladsch.flexmark.parser.delimiter.DelimiterRun;
+import com.vladsch.flexmark.util.ast.Node;
+
+/**
+ * @author shuzijun
+ */
+public class InlineLaTexProcessor implements DelimiterProcessor {
+
+ @Override
+ public void process(final Delimiter opener,
+ final Delimiter closer,
+ final int delimitersUsed) {
+ final InlineLaTexNode node = new InlineLaTexNode(opener, closer);
+ opener.moveNodesBetweenDelimitersTo(node, closer);
+ }
+
+ @Override
+ public char getOpeningCharacter() {
+ return '$';
+ }
+
+ @Override
+ public char getClosingCharacter() {
+ return '$';
+ }
+
+ @Override
+ public int getMinLength() {
+ return 1;
+ }
+
+ @Override
+ public int getDelimiterUse(final DelimiterRun opener, final DelimiterRun closer) {
+ return 1;
+ }
+
+ @Override
+ public boolean canBeOpener(final String before,
+ final String after,
+ final boolean leftFlanking,
+ final boolean rightFlanking,
+ final boolean beforeIsPunctuation,
+ final boolean afterIsPunctuation,
+ final boolean beforeIsWhitespace,
+ final boolean afterIsWhiteSpace) {
+ return leftFlanking;
+ }
+
+ @Override
+ public boolean canBeCloser(final String before,
+ final String after,
+ final boolean leftFlanking,
+ final boolean rightFlanking,
+ final boolean beforeIsPunctuation,
+ final boolean afterIsPunctuation,
+ final boolean beforeIsWhitespace,
+ final boolean afterIsWhiteSpace) {
+ return rightFlanking;
+ }
+
+ @Override
+ public Node unmatchedDelimiterNode(
+ final InlineParser inlineParser, final DelimiterRun delimiter) {
+ return null;
+ }
+
+ @Override
+ public boolean skipNonOpenerCloser() {
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/shuzijun/leetcode/plugin/window/JcefLogin.java b/src/main/java/com/shuzijun/leetcode/plugin/window/JcefLogin.java
index 0ec06d8a..279dba32 100644
--- a/src/main/java/com/shuzijun/leetcode/plugin/window/JcefLogin.java
+++ b/src/main/java/com/shuzijun/leetcode/plugin/window/JcefLogin.java
@@ -9,6 +9,7 @@
import com.shuzijun.leetcode.plugin.model.PluginConstant;
import com.shuzijun.leetcode.plugin.utils.HttpRequestUtils;
import com.shuzijun.leetcode.plugin.utils.LogUtils;
+import com.shuzijun.leetcode.plugin.utils.PropertiesUtils;
import com.shuzijun.leetcode.plugin.utils.URLUtils;
import org.cef.browser.CefBrowser;
import org.cef.browser.CefFrame;
@@ -135,7 +136,7 @@ public boolean visit(CefCookie cefCookie, int count, int total, BoolRef boolRef)
HttpRequestUtils.setCookie(cookieList);
if (HttpRequestUtils.isLogin()) {
HttpLogin.loginSuccess(tree, project, cookieList);
- browser.executeJavaScript("alert('Login is successful. close the window')", "leetcode-editor", 0);
+ browser.executeJavaScript("alert('"+ PropertiesUtils.getInfo("browser.login.success") +"')", "leetcode-editor", 0);
successDispose = true;
} else {
cookieList.clear();
diff --git a/src/main/java/com/shuzijun/leetcode/plugin/window/WindowFactory.java b/src/main/java/com/shuzijun/leetcode/plugin/window/WindowFactory.java
index a60ebec4..f63c0bb1 100644
--- a/src/main/java/com/shuzijun/leetcode/plugin/window/WindowFactory.java
+++ b/src/main/java/com/shuzijun/leetcode/plugin/window/WindowFactory.java
@@ -11,6 +11,8 @@
import com.intellij.ui.content.ContentFactory;
import com.shuzijun.leetcode.plugin.listener.UpdatePluginListener;
import com.shuzijun.leetcode.plugin.model.PluginConstant;
+import com.shuzijun.leetcode.plugin.setting.PersistentConfig;
+import icons.LeetCodeEditorIcons;
import org.apache.commons.lang.StringUtils;
import org.jetbrains.annotations.NotNull;
@@ -32,7 +34,9 @@ public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindo
navigatorPanel.addAncestorListener(new UpdatePluginListener());
Content content = contentFactory.createContent(navigatorPanel, "", false);
toolWindow.getContentManager().addContent(content);
-
+ if(PersistentConfig.getInstance().getInitConfig()!=null && !PersistentConfig.getInstance().getInitConfig().getShowToolIcon()){
+ toolWindow.setIcon(LeetCodeEditorIcons.EMPEROR_NEW_CLOTHES);
+ }
}
public static DataContext getDataContext(@NotNull Project project) {
diff --git a/src/main/java/icons/LeetCodeEditorIcons.java b/src/main/java/icons/LeetCodeEditorIcons.java
index 386be267..1e9f177f 100644
--- a/src/main/java/icons/LeetCodeEditorIcons.java
+++ b/src/main/java/icons/LeetCodeEditorIcons.java
@@ -9,32 +9,36 @@
*/
public interface LeetCodeEditorIcons {
- Icon LEETCODE_TOOL_WINDOW = IconLoader.getIcon("/icons/LeetCode.png");
+ Icon LEETCODE_TOOL_WINDOW = IconLoader.getIcon("/icons/LeetCode.svg");
+ Icon EMPEROR_NEW_CLOTHES = IconLoader.getIcon("/icons/emperor_new_clothes.svg");
+
+ Icon CLEAN = IconLoader.getIcon("/icons/clean.svg");
+ Icon CLEAR = IconLoader.getIcon("/icons/clear.svg");
+ Icon COLLAPSE = IconLoader.getIcon("/icons/collapse.svg");
+ Icon CONFIG = IconLoader.getIcon("/icons/config.svg");
+ Icon DESC = IconLoader.getIcon("/icons/desc.svg");
+ Icon EDIT_DOC = IconLoader.getIcon("/icons/edit_doc.svg");
+ Icon FAVORITE = IconLoader.getIcon("/icons/favorite.svg");
+ Icon FILTER = IconLoader.getIcon("/icons/filter.svg");
+ Icon FIND = IconLoader.getIcon("/icons/find.svg");
+ Icon HELP = IconLoader.getIcon("/icons/help.svg");
+ Icon HISTORY = IconLoader.getIcon("/icons/history.svg");
+ Icon LOGIN = IconLoader.getIcon("/icons/login.svg");
+ Icon LOGOUT = IconLoader.getIcon("/icons/logout.svg");
+ Icon POPUP = IconLoader.getIcon("/icons/popup.svg");
+ Icon POSITION = IconLoader.getIcon("/icons/position.svg");
+ Icon PROGRESS = IconLoader.getIcon("/icons/progress.svg");
+ Icon QUESTION = IconLoader.getIcon("/icons/question.svg");
+ Icon RANDOM = IconLoader.getIcon("/icons/random.svg");
+ Icon REFRESH = IconLoader.getIcon("/icons/refresh.svg");
+ Icon RUN = IconLoader.getIcon("/icons/run.svg");
+ Icon SOLUTION = IconLoader.getIcon("/icons/solution.svg");
+ Icon SUBMIT = IconLoader.getIcon("/icons/submit.svg");
+ Icon TIME = IconLoader.getIcon("/icons/time.svg");
+ Icon SORT_ASC = IconLoader.getIcon("/icons/sortAsc.svg");
+ Icon SORT_DESC = IconLoader.getIcon("/icons/sortDesc.svg");
+ Icon NOTE = IconLoader.getIcon("/icons/note.svg");
+ Icon LCV = IconLoader.getIcon("/icons/lcv.svg");
+ Icon DONATE = IconLoader.getIcon("/icons/donate.svg");
- Icon CLEAN = IconLoader.getIcon("/icons/clean.png");
- Icon CLEAR = IconLoader.getIcon("/icons/clear.png");
- Icon COLLAPSE = IconLoader.getIcon("/icons/collapse.png");
- Icon CONFIG = IconLoader.getIcon("/icons/config.png");
- Icon DESC = IconLoader.getIcon("/icons/desc.png");
- Icon EDIT_DOC = IconLoader.getIcon("/icons/edit_doc.png");
- Icon FAVORITE = IconLoader.getIcon("/icons/favorite.png");
- Icon FILTER = IconLoader.getIcon("/icons/filter.png");
- Icon FIND = IconLoader.getIcon("/icons/find.png");
- Icon HELP = IconLoader.getIcon("/icons/help.png");
- Icon HISTORY = IconLoader.getIcon("/icons/history.png");
- Icon LOGIN = IconLoader.getIcon("/icons/login.png");
- Icon LOGOUT = IconLoader.getIcon("/icons/logout.png");
- Icon POPUP = IconLoader.getIcon("/icons/popup.png");
- Icon POSITION = IconLoader.getIcon("/icons/position.png");
- Icon PROGRESS = IconLoader.getIcon("/icons/progress.png");
- Icon QUESTION = IconLoader.getIcon("/icons/question.png");
- Icon RANDOM = IconLoader.getIcon("/icons/random.png");
- Icon REFRESH = IconLoader.getIcon("/icons/refresh.png");
- Icon RUN = IconLoader.getIcon("/icons/run.png");
- Icon SOLUTION = IconLoader.getIcon("/icons/solution.png");
- Icon SUBMIT = IconLoader.getIcon("/icons/submit.png");
- Icon TIME = IconLoader.getIcon("/icons/time.png");
- Icon SORT_ASC = IconLoader.getIcon("/icons/sortAsc.png");
- Icon SORT_DESC = IconLoader.getIcon("/icons/sortDesc.png");
- Icon NOTE = IconLoader.getIcon("/icons/note.png");
}
diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml
index 480265fa..76982d84 100644
--- a/src/main/resources/META-INF/plugin.xml
+++ b/src/main/resources/META-INF/plugin.xml
@@ -1,7 +1,7 @@
leetcode-editor
- leetcode editor
- 6.9
+ LeetCode Editor
+ 7.0
shuzijun
com.intellij.modules.lang
- org.intellij.plugins.markdown
@@ -144,7 +143,7 @@
serviceImplementation="com.shuzijun.leetcode.plugin.setting.PersistentConfig"/>
-
@@ -163,6 +162,10 @@
+
+
+
@@ -247,6 +250,9 @@
+
+