Java tutorial
/* * Copyright (c) 2011-2014 Julien Nicoulaud <julien.nicoulaud@gmail.com> * Copyright (c) 2015-2015 Vladimir Schneider <vladimir.schneider@gmail.com> * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package com.vladsch.idea.multimarkdown.editor; import com.intellij.codeHighlighting.BackgroundEditorHighlighter; import com.intellij.ide.structureView.StructureViewBuilder; import com.intellij.lang.Language; import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.command.UndoConfirmationPolicy; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.CaretModel; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.EditorFactory; import com.intellij.openapi.editor.event.DocumentAdapter; import com.intellij.openapi.editor.event.DocumentEvent; import com.intellij.openapi.editor.ex.DocumentEx; import com.intellij.openapi.editor.highlighter.EditorHighlighterFactory; import com.intellij.openapi.editor.impl.EditorImpl; import com.intellij.openapi.fileEditor.*; import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.UserDataHolderBase; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.ui.components.JBScrollPane; import com.vladsch.idea.multimarkdown.MultiMarkdownBundle; import com.vladsch.idea.multimarkdown.MultiMarkdownPlugin; import com.vladsch.idea.multimarkdown.MultiMarkdownProjectComponent; import com.vladsch.idea.multimarkdown.parser.MultiMarkdownLexParserManager; import com.vladsch.idea.multimarkdown.settings.MultiMarkdownGlobalSettings; import com.vladsch.idea.multimarkdown.settings.MultiMarkdownGlobalSettingsListener; import com.vladsch.idea.multimarkdown.util.ReferenceChangeListener; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.pegdown.Extensions; import org.pegdown.LinkRenderer; import org.pegdown.ToHtmlSerializer; import org.pegdown.ast.RootNode; import javax.swing.*; import javax.swing.text.DefaultCaret; import javax.swing.text.html.HTMLEditorKit; import javax.swing.text.html.StyleSheet; import java.awt.*; import java.beans.PropertyChangeListener; import java.io.IOException; import java.io.StringReader; import java.util.Timer; import java.util.TimerTask; import java.util.regex.Matcher; import java.util.regex.Pattern; import static com.vladsch.idea.multimarkdown.editor.MultiMarkdownPathResolver.isWikiDocument; import static org.apache.commons.lang.StringEscapeUtils.escapeHtml; public class MultiMarkdownPreviewEditor extends UserDataHolderBase implements FileEditor { private static final Logger logger = Logger.getInstance(MultiMarkdownPreviewEditor.class); public static final String PREVIEW_EDITOR_NAME = MultiMarkdownBundle.message("multimarkdown.preview-tab-name"); public static final String TEXT_EDITOR_NAME = MultiMarkdownBundle.message("multimarkdown.html-tab-name"); protected static int instances = 0; /** * The {@link java.awt.Component} used to render the HTML preview. */ protected final JEditorPane jEditorPane; /** * The {@link JBScrollPane} allowing to browse {@link #jEditorPane}. */ protected final JBScrollPane scrollPane; /** * The {@link Document} previewed in this editor. */ protected final Document document; protected final boolean isWikiDocument; //private final EditorTextField myTextViewer; private final EditorImpl myTextViewer; private boolean isReleased = false; protected MultiMarkdownGlobalSettingsListener globalSettingsListener; protected ReferenceChangeListener projectFileListener; private boolean isActive = false; private boolean isRawHtml = false; private boolean isEditorTabVisible = true; private Project project; private LinkRenderer linkRendererNormal; private LinkRenderer linkRendererModified; public static boolean isShowModified() { return MultiMarkdownGlobalSettings.getInstance().showHtmlTextAsModified.getValue(); } public static int getParsingTimeout() { return MultiMarkdownGlobalSettings.getInstance().parsingTimeout.getValue(); } public static int getUpdateDelay() { return MultiMarkdownGlobalSettings.getInstance().updateDelay.getValue(); } public static boolean isTaskLists() { return MultiMarkdownGlobalSettings.getInstance().taskLists.getValue(); } public static boolean isDarkTheme() { return MultiMarkdownGlobalSettings.getInstance().isDarkUITheme(); } public static String getCustomCss() { return MultiMarkdownGlobalSettings.getInstance().customCss.getValue(); } public static boolean isShowHtmlText() { return MultiMarkdownGlobalSettings.getInstance().showHtmlText.getValue(); } /** * Indicates whether the HTML preview is obsolete and should regenerated from the Markdown {@link #document}. */ protected boolean previewIsObsolete = true; protected Timer updateDelayTimer; protected final int instance = ++instances; protected void updateEditorTabIsVisible() { if (isRawHtml) { isEditorTabVisible = isShowHtmlText(); getComponent().setVisible(isEditorTabVisible); } else { isEditorTabVisible = true; } } protected void checkNotifyUser() { //final Project project = this.project; //final MultiMarkdownGlobalSettings settings = MultiMarkdownGlobalSettings.getInstance(); // //settings.startSuspendNotifications(); //if (settings.isDarkUITheme() && (settings.iconBullets.getValue() || settings.iconTasks.getValue()) && true && !settings.wasShownDarkBug.getValue()) { // // notify the user that the Icons for Tasks and Bullets will be turned off due to a rendering bug // settings.wasShownDarkBug.setValue(true); // NotificationGroup issueNotificationGroup = new NotificationGroup(MultiMarkdownGlobalSettings.NOTIFICATION_GROUP_ISSUES, // NotificationDisplayType.BALLOON, true, null); // // Notification notification = issueNotificationGroup.createNotification("<strong>MultiMarkdown</strong> Plugin Notification", // "<p>An issue with rendering icons when the UI theme is <strong>Darcula</strong> prevents bullet "+ // "and task list items from using these options. " + // "These settings will be ignored while <strong>Darcula</strong> "+ // "theme is in effect and until the issue is fixed.</p>\n" + // "<p> </p>\n" + // "<p>Feel free leave the <em>Bullets with Icons</em> and <em>Tasks with Icons</em> options turned on. "+ // "They will take effect when they no longer adversely affect the display.</p>\n" + // "", // NotificationType.INFORMATION, null); // notification.setImportant(true); // Notifications.Bus.notify(notification, project); //} //settings.endSuspendNotifications(); } protected void updateLinkRenderer() { int options = 0; if (MultiMarkdownGlobalSettings.getInstance().githubWikiLinks.getValue()) options |= MultiMarkdownLinkRenderer.GITHUB_WIKI_LINK_FORMAT; linkRendererModified = new MultiMarkdownLinkRenderer(project, document, "absent", null, options | MultiMarkdownLinkRenderer.VALIDATE_LINKS); linkRendererNormal = new MultiMarkdownLinkRenderer(options); } /** * Build a new instance of {@link MultiMarkdownPreviewEditor}. * * @param project the {@link Project} containing the document * @param document the {@link com.intellij.openapi.editor.Document} previewed in this editor. */ public MultiMarkdownPreviewEditor(@NotNull final Project project, @NotNull Document document, boolean isRawHtml) { this.isRawHtml = isRawHtml; this.document = document; this.project = project; this.isWikiDocument = isWikiDocument(document); // Listen to the document modifications. this.document.addDocumentListener(new DocumentAdapter() { @Override public void documentChanged(DocumentEvent e) { delayedHtmlPreviewUpdate(false); } }); // Listen to settings changes MultiMarkdownGlobalSettings.getInstance() .addListener(globalSettingsListener = new MultiMarkdownGlobalSettingsListener() { public void handleSettingsChanged(@NotNull final MultiMarkdownGlobalSettings newSettings) { if (project.isDisposed()) return; updateEditorTabIsVisible(); updateLinkRenderer(); delayedHtmlPreviewUpdate(true); checkNotifyUser(); } }); MultiMarkdownProjectComponent projectComponent = MultiMarkdownPlugin.getProjectComponent(project); if (projectComponent != null) { projectComponent.addListener(projectFileListener = new ReferenceChangeListener() { @Override public void referenceChanged(@Nullable String name) { if (project.isDisposed()) return; delayedHtmlPreviewUpdate(false); } }); } project.getMessageBus().connect(this).subscribe(DumbService.DUMB_MODE, new DumbService.DumbModeListener() { @Override public void enteredDumbMode() { } @Override public void exitDumbMode() { // need to re-evaluate class link accessibility if (project.isDisposed()) return; delayedHtmlPreviewUpdate(false); } }); updateLinkRenderer(); if (isRawHtml) { jEditorPane = null; scrollPane = null; Language language = Language.findLanguageByID("HTML"); FileType fileType = language != null ? language.getAssociatedFileType() : null; //myTextViewer = new EditorTextField(EditorFactory.getInstance().createDocument(""), project, fileType, true, false); Document myDocument = EditorFactory.getInstance().createDocument(""); myTextViewer = (EditorImpl) EditorFactory.getInstance().createViewer(myDocument, project); if (fileType != null) myTextViewer.setHighlighter( EditorHighlighterFactory.getInstance().createEditorHighlighter(project, fileType)); } else { // Setup the editor pane for rendering HTML. myTextViewer = null; jEditorPane = new JEditorPane(); //jEditorPane = new BrowserPane(); scrollPane = new JBScrollPane(jEditorPane); setStyleSheet(); // Add a custom link listener which can resolve local link references. jEditorPane.addHyperlinkListener(new MultiMarkdownLinkListener(jEditorPane, project, document)); jEditorPane.setEditable(false); // Set the editor pane caret position to top left, and do not let it reset it jEditorPane.getCaret().setMagicCaretPosition(new Point(0, 0)); ((DefaultCaret) jEditorPane.getCaret()).setUpdatePolicy(DefaultCaret.NEVER_UPDATE); } checkNotifyUser(); } protected String makeHtmlPage(String html) { VirtualFile file = FileDocumentManager.getInstance().getFile(document); // scan for <table>, </table>, <tr>, </tr> and other tags we modify, this could be done with a custom plugin to pegdown but // then it would be more trouble to get un-modified HTML. String regex = "(<table>|<thead>|<tbody>|<tr>|<hr/>|<del>|</del>|</p>|<kbd>|</kbd>|<var>|</var>";//|<code>|</code>"; StringBuilder result = new StringBuilder(html.length() + (html.length() >> 2)); String gitHubHref = MultiMarkdownPathResolver.getGitHubDocumentURL(project, document, !isWikiDocument); String gitHubClose = ""; if (gitHubHref == null) { gitHubHref = ""; } else { gitHubHref = "<a href=\"" + gitHubHref + "\" name=\"wikipage\" id=\"wikipage\">"; gitHubClose = "</a>"; } if (isWikiDocument) { result.append("<body class=\"multimarkdown-wiki-preview\">\n<div class=\"content\">\n"); result.append("" + "<h1 class=\"first-child\">").append(gitHubHref) .append(escapeHtml(file == null ? "" : file.getNameWithoutExtension().replace('-', ' '))) .append(gitHubClose).append("</h1>\n").append(""); } else { result.append("<body class=\"multimarkdown-preview\">\n<div class=\"content\">\n" + "<div class=\"page-header\">").append(gitHubHref) .append(escapeHtml(file == null ? "" : file.getName().replace('-', ' '))).append(gitHubClose) .append("</div>\n").append("<div class=\"hr\"></div>\n").append(""); // for now nothing regex += "|<h1>"; } String regexTail = "|<li>\\n*\\s*<p>"; boolean isDarkTheme = isDarkTheme(); boolean taskLists = isTaskLists(); if (taskLists) { regex += "|<li class=\"task-list-item\">\\n*\\s*<p>|<br\\s*/?>|<li class=\"task-list-item\">|<li>\\[(?:x|X)\\]\\s*|<li>\\[ \\]\\s*|<li>\\n*\\s*<p>\\[x\\]\\s*|<li>\\n*\\s*<p>\\[ \\]\\s*"; } regex += regexTail; regex += ")"; Pattern p = Pattern.compile(regex, Pattern.CASE_INSENSITIVE); Matcher m = p.matcher(html); int lastPos = 0; int rowCount = 0; boolean[] isOrderedList = new boolean[20]; int listDepth = -1; boolean firstChildH1 = !isWikiDocument; while (m.find()) { String found = m.group(); if (lastPos < m.start(0)) { result.append(html.substring(lastPos, m.start(0))); } if (found.equals("</p>")) { result.append(found); } else if (found.startsWith("<br")) { result.append("<br/>\n"); } else if (found.equals("<table>")) { rowCount = 0; result.append(found); } else if (found.equals("<thead>")) { result.append(found); } else if (found.equals("<tbody>")) { result.append(found); } else if (found.equals("/>")) { result.append(">"); } else if (found.equals("<tr>")) { rowCount++; result.append("<tr class=\"") .append(rowCount == 1 ? "first-child" : (rowCount & 1) != 0 ? "odd-child" : "even-child") .append("\">"); } else if (found.equals("<hr/>")) { result.append("<div class=\"hr\"> </div>"); } else if (found.equals("<h1>")) { result.append(firstChildH1 ? "<h1 class=\"first-child\">" : "<h1>"); firstChildH1 = false; } else if (found.equals("<del>")) { result.append("<span class=\"del\">"); } else if (found.equals("</del>")) { result.append("</span>"); } else if (found.equals("<kbd>")) { result.append("<span class=\"kbd\">"); } else if (found.equals("</kbd>")) { result.append("</span>"); } else if (found.equals("<code>")) { result.append("<span class=\"code\">"); } else if (found.equals("</code>")) { result.append("</span>"); } else if (found.equals("<var>")) { result.append("<span class=\"var\">"); } else if (found.equals("</var>")) { result.append("</span>"); } else { found = found.trim(); if (taskLists && found.equals("<li>[x]")) { result.append("<li class=\"dtask\">"); } else if (taskLists && found.equals("<li>[X]")) { result.append("<li class=\"dtask\">"); } else if (taskLists && found.equals("<li>[ ]")) { result.append("<li class=\"dtask\">"); } else if (taskLists && found.equals("<li class=\"task-list-item\">")) { result.append("<li class=\"taski\">"); } else { // here we have <li>\n*\s*<p>, need to strip out \n*\s* so we can match them easier String foundWithP = found; foundWithP = foundWithP.replaceAll("<li>\\n*\\s*<p>", "<li><p>"); found = foundWithP.replaceAll("<li class=\"task-list-item\">\\n*\\s*<p>", "<li class=\"task\"><p>"); found = found.trim(); if (found.equals("<li><p>")) { result.append("<li class=\"p\"><p class=\"p\">"); } else if (taskLists && found.equals("<li><p>[x]")) { result.append("<li class=\"dtaskp\"><p class=\"p\">"); } else if (taskLists && found.equals("<li><p>[ ]")) { result.append("<li class=\"dtaskp\"><p class=\"p\">"); } else if (taskLists && found.equals("<li class=\"task-list-item\"><p>")) { result.append("<li class=\"taskp\"><p class=\"p\">"); } else { result.append(found); } } } lastPos = m.end(0); } if (lastPos < html.length()) { result.append(html.substring(lastPos)); } result.append("\n</div>\n</body>\n"); return result.toString(); } protected void delayedHtmlPreviewUpdate(final boolean fullKit) { if (updateDelayTimer != null) { updateDelayTimer.cancel(); updateDelayTimer = null; } if (project.isDisposed()) return; if (!isEditorTabVisible) return; updateDelayTimer = new Timer(); updateDelayTimer.schedule(new TimerTask() { @Override public void run() { if (project.isDisposed()) return; ApplicationManager.getApplication().invokeLater(new Runnable() { @Override public void run() { if (project.isDisposed()) return; previewIsObsolete = true; if (fullKit) { setStyleSheet(); //processor.remove(); // make it re-initialize when accessed } updateHtmlContent(isActive || isMyTabSelected()); } }, ModalityState.any()); } }, getUpdateDelay()); } protected boolean isMyTabSelected() { FileEditorManager manager = FileEditorManager.getInstance(project); FileEditor[] editors = manager.getSelectedEditors(); for (FileEditor editor : editors) { if (editor == this) return true; } return false; } protected MultiMarkdownPreviewEditor findCounterpart() { // here we can find our HTML Text counterpart and update its HTML at the same time. but it is better to keep it separate for now VirtualFile file = FileDocumentManager.getInstance().getFile(document); if (file != null) { FileEditorManager manager = FileEditorManager.getInstance(project); FileEditor[] editors = manager.getEditors(file); for (FileEditor editor : editors) { if (editor != this && editor instanceof MultiMarkdownPreviewEditor) { return (MultiMarkdownPreviewEditor) editor; } } } return null; } protected void setStyleSheet() { if (isRawHtml) return; MultiMarkdownEditorKit htmlKit = new MultiMarkdownEditorKit(); final StyleSheet style = new MultiMarkdownStyleSheet(); if (!MultiMarkdownGlobalSettings.getInstance().useCustomCss(false)) { style.importStyleSheet(MultiMarkdownGlobalSettings.getInstance().getCssFileURL(false)); } else { try { style.loadRules(new StringReader(MultiMarkdownGlobalSettings.getInstance().getCssText(false)), null); } catch (IOException e) { e.printStackTrace(); } } htmlKit.setStyleSheet(style); jEditorPane.setEditorKit(htmlKit); } public static void setStyleSheet(JEditorPane jEditorPane) { HTMLEditorKit htmlKit = new HTMLEditorKit(); final StyleSheet style = new StyleSheet(); if (!MultiMarkdownGlobalSettings.getInstance().useCustomCss(false)) { style.importStyleSheet(MultiMarkdownGlobalSettings.getInstance().getCssFileURL(false)); } else { try { style.loadRules(new StringReader(MultiMarkdownGlobalSettings.getInstance().getCssText(false)), null); } catch (IOException e) { e.printStackTrace(); } } htmlKit.setStyleSheet(style); jEditorPane.setEditorKit(htmlKit); } protected void updateRawHtmlText(final String htmlTxt) { final DocumentEx myDocument = myTextViewer.getDocument(); if (project.isDisposed()) return; ApplicationManager.getApplication().runWriteAction(new Runnable() { @Override public void run() { if (project.isDisposed()) return; CommandProcessor.getInstance().executeCommand(project, new Runnable() { @Override public void run() { if (project.isDisposed()) return; myDocument.replaceString(0, myDocument.getTextLength(), htmlTxt); final CaretModel caretModel = myTextViewer.getCaretModel(); if (caretModel.getOffset() >= myDocument.getTextLength()) { caretModel.moveToOffset(myDocument.getTextLength()); } } }, null, null, UndoConfirmationPolicy.DEFAULT, myDocument); } }); } protected String markdownToHtml(boolean modified, RootNode rootNode) { if (rootNode == null) { return "<strong>Parser timed out</strong>"; } else { if (modified) { MultiMarkdownToHtmlSerializer htmlSerializer = new MultiMarkdownToHtmlSerializer(project, document, linkRendererModified); if (!isWikiDocument) { htmlSerializer.setFlag(MultiMarkdownToHtmlSerializer.NO_WIKI_LINKS); } return htmlSerializer.toHtml(rootNode); } else { return new ToHtmlSerializer(linkRendererNormal).toHtml(rootNode).replace("<br/>", "<br/>\n"); } } } protected void updateHtmlContent(boolean force) { if (updateDelayTimer != null) { updateDelayTimer.cancel(); updateDelayTimer = null; } if (previewIsObsolete && isEditorTabVisible && (isActive || force)) { try { int options = MultiMarkdownGlobalSettings.getInstance().getExtensionsValue(); int pegdownExtensions = (options & ~Extensions.TASKLISTITEMS) | ((options & Extensions.EXTANCHORLINKS) != 0 ? Extensions.EXTANCHORLINKS_WRAP : 0); RootNode rootNode = MultiMarkdownLexParserManager.parseMarkdownRoot(document.getCharsSequence(), pegdownExtensions, getParsingTimeout()); if (isRawHtml) { updateRawHtmlText(isShowModified() ? makeHtmlPage(markdownToHtml(true, rootNode)) : markdownToHtml(false, rootNode)); } else { jEditorPane.setText(makeHtmlPage(markdownToHtml(true, rootNode))); } previewIsObsolete = false; // here we can find our HTML Text counterpart but it is better to keep it separate for now //VirtualFile file = FileDocumentManager.getInstance().getFile(document); //FileEditorManager manager = FileEditorManager.getInstance(project); //FileEditor[] editors = manager.getEditors(file); //for (int i = 0; i < editors.length; i++) //{ // if (editors[i] == this) // { // if (editors.length > i && editors[i+1] instanceof MultiMarkdownPreviewEditor) { // // update its html too // MultiMarkdownPreviewEditor htmlEditor = (MultiMarkdownPreviewEditor)editors[i+1]; // boolean showModified = MultiMarkdownGlobalSettings.getInstance().isShowHtmlTextAsModified(); // htmlEditor.setHtmlContent("<div id=\"multimarkdown-preview\">\n" + (showModified ? procHtml : html) + "\n</div>\n"); // break; // } // } //} } catch (Exception e) { logger.info("Failed processing Markdown document", e); } } } public void setHtmlContent(String html) { jEditorPane.setText(html); } /** * Get the {@link java.awt.Component} to display as this editor's UI. * * @return a scrollable {@link JEditorPane}. */ @NotNull public JComponent getComponent() { return scrollPane != null ? scrollPane : myTextViewer.getComponent(); } /** * Get the component to be focused when the editor is opened. * * @return {@link #scrollPane} */ @Nullable public JComponent getPreferredFocusedComponent() { return scrollPane != null ? scrollPane : myTextViewer.getContentComponent(); } /** * Get the editor displayable name. * * @return editor name */ @NotNull @NonNls public String getName() { return isRawHtml ? TEXT_EDITOR_NAME : PREVIEW_EDITOR_NAME; } /** * Get the state of the editor. * <p/> * Just returns {@link FileEditorState#INSTANCE} as {@link MultiMarkdownPreviewEditor} is stateless. * * @param level the level. * @return {@link FileEditorState#INSTANCE} * @see #setState(com.intellij.openapi.fileEditor.FileEditorState) */ @NotNull public FileEditorState getState(@NotNull FileEditorStateLevel level) { return FileEditorState.INSTANCE; } /** * Set the state of the editor. * <p/> * Does not do anything as {@link MultiMarkdownPreviewEditor} is stateless. * * @param state the new state. * @see #getState(com.intellij.openapi.fileEditor.FileEditorStateLevel) */ public void setState(@NotNull FileEditorState state) { } /** * Indicates whether the document content is modified compared to its file. * * @return {@code false} as {@link MultiMarkdownPreviewEditor} is read-only. */ public boolean isModified() { return false; } /** * Indicates whether the editor is valid. * * @return {@code true} if {@link #document} content is readable. */ public boolean isValid() { return true; } /** * Invoked when the editor is selected. * <p/> * Update the HTML content if obsolete. */ public void selectNotify() { isActive = true; if (previewIsObsolete) { updateHtmlContent(false); } } /** * Invoked when the editor is deselected. * <p/> * Does nothing. */ public void deselectNotify() { isActive = false; } /** * Add specified listener. * <p/> * Does nothing. * * @param listener the listener. */ public void addPropertyChangeListener(@NotNull PropertyChangeListener listener) { } /** * Remove specified listener. * <p/> * Does nothing. * * @param listener the listener. */ public void removePropertyChangeListener(@NotNull PropertyChangeListener listener) { } /** * Get the background editor highlighter. * * @return {@code null} as {@link MultiMarkdownPreviewEditor} does not require highlighting. */ @Nullable public BackgroundEditorHighlighter getBackgroundHighlighter() { return null; } /** * Get the current location. * * @return {@code null} as {@link MultiMarkdownPreviewEditor} is not navigable. */ @Nullable public FileEditorLocation getCurrentLocation() { return null; } /** * Get the structure view builder. * * @return TODO {@code null} as parsing/PSI is not implemented. */ @Nullable public StructureViewBuilder getStructureViewBuilder() { return null; } /** * Dispose the editor. */ public void dispose() { if (!isReleased) { isReleased = true; if (updateDelayTimer != null) { updateDelayTimer.cancel(); updateDelayTimer = null; } if (jEditorPane != null) { jEditorPane.removeAll(); } if (globalSettingsListener != null) { MultiMarkdownGlobalSettings.getInstance().removeListener(globalSettingsListener); globalSettingsListener = null; } if (myTextViewer != null) { final Application application = ApplicationManager.getApplication(); final Runnable runnable = new Runnable() { @Override public void run() { if (!myTextViewer.isDisposed()) { EditorFactory.getInstance().releaseEditor(myTextViewer); } } }; if (application.isUnitTestMode() || application.isDispatchThread()) { runnable.run(); } else { application.invokeLater(runnable); } } Disposer.dispose(this); } } }