added simple preview for restructured text

This commit is contained in:
Ekaterina Tuzova
2018-06-18 16:41:11 +03:00
parent df7ffb436a
commit 0f8e7428ed
7 changed files with 316 additions and 0 deletions
@@ -26,4 +26,8 @@
</action>
</actions>
<extensions defaultExtensionNs="restructured.text">
<html.preview.provider implementation="com.jetbrains.rest.RestPythonPreviewProvider" />
</extensions>
</idea-plugin>
@@ -0,0 +1,43 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.rest;
import com.google.common.collect.Lists;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.execution.process.ProcessOutput;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.roots.ProjectFileIndex;
import com.intellij.openapi.vfs.CharsetToolkit;
import com.intellij.openapi.vfs.VirtualFile;
import com.jetbrains.python.sdk.PySdkUtil;
import com.jetbrains.python.sdk.PythonSdkType;
import com.jetbrains.rest.editor.RestPreviewProvider;
import org.jetbrains.annotations.Nullable;
import static com.jetbrains.python.PythonHelper.REST_RUNNER;
public class RestPythonPreviewProvider extends RestPreviewProvider {
@Nullable
public String toHtml(String text, VirtualFile virtualFile, Project project) {
Module module = ProjectFileIndex.SERVICE.getInstance(project).getModuleForFile(virtualFile);
if (module == null) return null;
final Sdk sdk = PythonSdkType.findPythonSdk(module);
if (sdk == null) {
return null;
}
final String sdkHomePath = sdk.getHomePath();
if (sdkHomePath == null) {
return null;
}
final GeneralCommandLine commandLine = REST_RUNNER.newCommandLine(sdkHomePath, Lists.newArrayList("rst2html"));
final ProcessOutput output = PySdkUtil.getProcessOutput(commandLine, virtualFile.getParent().getPath(), null, 5000,
text.getBytes(CharsetToolkit.UTF8_CHARSET), true);
if (output.isCancelled() || output.isTimeout()) return null;
return output.getStdout();
}
}
@@ -15,5 +15,6 @@
<orderEntry type="library" name="Guava" level="project" />
<orderEntry type="module" module-name="intellij.spellchecker" />
<orderEntry type="module" module-name="intellij.platform.testFramework" scope="TEST" />
<orderEntry type="library" name="com.googlecode.owasp-java-html-sanitizer:owasp-java-html-sanitizer:20180219.1" level="project" />
</component>
</module>
+6
View File
@@ -1,4 +1,8 @@
<idea-plugin>
<extensionPoints>
<extensionPoint qualifiedName="restructured.text.html.preview.provider" interface="com.jetbrains.rest.editor.RestPreviewProvider" />
</extensionPoints>
<extensions defaultExtensionNs="com.intellij">
<fileTypeFactory implementation="com.jetbrains.rest.RestFileTypeFactory"/>
@@ -25,6 +29,8 @@
<indexPatternBuilder implementation="com.jetbrains.rest.RestIndexPatternBuilder" />
<todoIndexer filetype="ReST" implementationClass="com.jetbrains.rest.RestTodoIndexer" />
<fileEditorProvider implementation="com.jetbrains.rest.editor.RestSplitEditorProvider"/>
</extensions>
<extensions defaultExtensionNs="com.intellij.spellchecker">
@@ -0,0 +1,201 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.rest.editor;
import com.intellij.codeHighlighting.BackgroundEditorHighlighter;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.editor.event.DocumentListener;
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.NotNullLazyValue;
import com.intellij.openapi.util.UserDataHolderBase;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.javafx.JavaFxHtmlPanel;
import com.intellij.util.Alarm;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.owasp.html.HtmlPolicyBuilder;
import org.owasp.html.PolicyFactory;
import org.owasp.html.Sanitizers;
import javax.swing.*;
import java.beans.PropertyChangeListener;
public class RestPreviewFileEditor extends UserDataHolderBase implements FileEditor {
private final static long PARSING_CALL_TIMEOUT_MS = 50L;
private final static long RENDERING_DELAY_MS = 20L;
final static NotNullLazyValue<PolicyFactory> SANITIZER_VALUE = new NotNullLazyValue<PolicyFactory>() {
@NotNull
@Override
protected PolicyFactory compute() {
return Sanitizers.BLOCKS
.and(Sanitizers.FORMATTING)
.and(new HtmlPolicyBuilder()
.allowUrlProtocols("file", "http", "https").allowElements("img")
.allowAttributes("alt", "src", "title").onElements("img")
.allowAttributes("border", "height", "width").onElements("img")
.toFactory())
.and(new HtmlPolicyBuilder()
.allowUrlProtocols("file", "http", "https", "mailto").allowElements("a")
.allowAttributes("href", "title").onElements("a")
.toFactory())
.and(Sanitizers.TABLES)
.and(new HtmlPolicyBuilder()
.allowElements("code", "tr")
.allowAttributes("class").onElements("code", "tr")
.toFactory());
}
};
@NotNull
private final JavaFxHtmlPanel myPanel;
@NotNull
private final VirtualFile myFile;
private final Project myProject;
@Nullable
private final Document myDocument;
@NotNull
private final Alarm myPooledAlarm = new Alarm(Alarm.ThreadToUse.POOLED_THREAD, this);
@NotNull
private final Alarm mySwingAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD, this);
private final Object REQUESTS_LOCK = new Object();
@Nullable
private Runnable myLastRequest = null;
@NotNull
private String myLastRenderedHtml = "";
public RestPreviewFileEditor(@NotNull VirtualFile file, Project project) {
myFile = file;
myProject = project;
myDocument = FileDocumentManager.getInstance().getDocument(myFile);
myPanel = new JavaFxHtmlPanel();
if (myDocument != null) {
myDocument.addDocumentListener(new DocumentListener() {
@Override
public void beforeDocumentChange(DocumentEvent e) {
myPooledAlarm.cancelAllRequests();
}
@Override
public void documentChanged(final DocumentEvent e) {
myPooledAlarm.addRequest(() -> updateHtml(), PARSING_CALL_TIMEOUT_MS);
}
}, this);
}
}
@NotNull
@Override
public JComponent getComponent() {
return myPanel.getComponent();
}
@Nullable
@Override
public JComponent getPreferredFocusedComponent() {
return myPanel.getComponent();
}
@NotNull
@Override
public String getName() {
return "ReStructuredText HTML Preview";
}
@Override
public void setState(@NotNull FileEditorState state) {
}
@Override
public boolean isModified() {
return false;
}
@Override
public boolean isValid() {
return true;
}
@Override
public void selectNotify() {
myPooledAlarm.cancelAllRequests();
myPooledAlarm.addRequest(() -> updateHtml(), 0);
}
/**
* Is always run from pooled thread
*/
private void updateHtml() {
if (!myFile.isValid() || myDocument == null || Disposer.isDisposed(this)) {
return;
}
final String html = RestPreviewProvider.getProviders()[0].toHtml(myDocument.getText(), myFile, myProject);
if (html == null) {
return;
}
// EA-75860: The lines to the top may be processed slowly; Since we're in pooled thread, we can be disposed already.
if (!myFile.isValid() || Disposer.isDisposed(this)) {
return;
}
synchronized (REQUESTS_LOCK) {
if (myLastRequest != null) {
mySwingAlarm.cancelRequest(myLastRequest);
}
myLastRequest = () -> {
final String currentHtml = "<html>" + SANITIZER_VALUE.getValue().sanitize(html) + "</html>";
if (!currentHtml.equals(myLastRenderedHtml)) {
myLastRenderedHtml = currentHtml;
myPanel.setHtml(myLastRenderedHtml);
}
myPanel.render();
synchronized (REQUESTS_LOCK) {
myLastRequest = null;
}
};
mySwingAlarm.addRequest(myLastRequest, RENDERING_DELAY_MS, ModalityState.stateForComponent(getComponent()));
}
}
@Override
public void deselectNotify() {
}
@Override
public void addPropertyChangeListener(@NotNull PropertyChangeListener listener) {
}
@Override
public void removePropertyChangeListener(@NotNull PropertyChangeListener listener) {
}
@Nullable
@Override
public BackgroundEditorHighlighter getBackgroundHighlighter() {
return null;
}
@Nullable
@Override
public FileEditorLocation getCurrentLocation() {
return null;
}
@Override
public void dispose() {
Disposer.dispose(myPanel);
}
}
@@ -0,0 +1,22 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.rest.editor;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public abstract class RestPreviewProvider {
public static final ExtensionPointName<RestPreviewProvider> EP_NAME =
ExtensionPointName.create("restructured.text.html.preview.provider");
@Nullable
public abstract String toHtml(String text, VirtualFile virtualFile, Project project);
@NotNull
public static RestPreviewProvider[] getProviders() {
return EP_NAME.getExtensions();
}
}
@@ -0,0 +1,39 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.rest.editor;
import com.intellij.openapi.fileEditor.*;
import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.jetbrains.rest.RestFileType;
import org.jetbrains.annotations.NotNull;
public class RestSplitEditorProvider implements FileEditorProvider, DumbAware {
public RestSplitEditorProvider() {
}
@Override
public boolean accept(@NotNull Project project, @NotNull VirtualFile file) {
return file.getFileType() == RestFileType.INSTANCE;
}
@NotNull
@Override
public FileEditor createEditor(@NotNull Project project, @NotNull VirtualFile file) {
TextEditor editor = (TextEditor)TextEditorProvider.getInstance().createEditor(project, file);
return new TextEditorWithPreview(editor, new RestPreviewFileEditor(file, project));
}
@NotNull
@Override
public String getEditorTypeId() {
return "restructured-text-editor";
}
@NotNull
@Override
public FileEditorPolicy getPolicy() {
return FileEditorPolicy.HIDE_DEFAULT_EDITOR;
}
}