BASE_DIR_CONTEXT = new DataKey<>("LSP_BASE_DIR", (Path) null);
+ private final Project project;
private final Parser htmlParser;
private final HtmlRenderer htmlRenderer;
private final MutableDataSet options;
@@ -62,16 +73,18 @@ private MarkdownConverter(Project project) {
options.set(HtmlRenderer.SOFT_BREAK, "
\n");
options.set(HtmlRenderer.GENERATE_HEADER_ID, true);
- htmlRenderer = HtmlRenderer.builder(options)
- .nodeRendererFactory(new NodeRendererFactory() {
- @Override
- public NodeRenderer apply(DataHolder options) {
- return new SyntaxColorationCodeBlockRenderer(project, null, null);
- }
- }).build();
+ options.set(PROJECT_CONTEXT, project);
+ htmlRenderer = createHtmlRenderer(options);
htmlParser = Parser.builder(options).build();
}
+ @NotNull
+ private static HtmlRenderer createHtmlRenderer(MutableDataSet options) {
+ return HtmlRenderer.builder(options)
+ .linkResolverFactory(new LSPLinkResolver.Factory())
+ .nodeRendererFactory(new SyntaxColorationCodeBlockRenderer.Factory())
+ .build();
+ }
/**
* Convert the given markdown
to Html.
@@ -91,11 +104,43 @@ public NodeRenderer apply(DataHolder options) {
* the syntax coloration to use if MarkDown content contains some code block or blockquote to highlight.
* @return the given markdown
to Html.
*/
- public @NotNull String toHtml(@NotNull String markdown, @Nullable PsiFile file) {
- return toHtml(markdown, file != null ? file.getLanguage() : null, file != null ? file.getName() : null);
+ public @NotNull String toHtml(@NotNull String markdown,
+ @Nullable PsiFile file) {
+ var htmlRenderer = this.htmlRenderer;
+ if (file != null) {
+ // The HtmlRenderer is stored in LSPFileSupport instead of PsiFile
+ // to evict it when file is closed.
+ var fileSupport = LSPFileSupport.getSupport(file);
+ htmlRenderer = fileSupport.getUserData(HTML_RENDERER_KEY);
+ if (htmlRenderer == null) {
+ htmlRenderer = getHtmlRenderer(fileSupport);
+ }
+ }
+ return htmlRenderer.render(htmlParser.parse(markdown));
+ }
+
+ private synchronized HtmlRenderer getHtmlRenderer(@NotNull LSPFileSupport fileSupport) {
+ var file = fileSupport.getFile();
+ var htmlRenderer = fileSupport.getUserData(HTML_RENDERER_KEY);
+ if (htmlRenderer != null) {
+ return htmlRenderer;
+ }
+
+ MutableDataSet fileOptions = new MutableDataSet(options);
+ fileOptions.set(LANGUAGE_CONTEXT, file.getLanguage());
+ fileOptions.set(FILE_NAME_CONTEXT, file.getName());
+ Path baseDir = file.getVirtualFile().getParent().getFileSystem().getNioPath(file.getVirtualFile().getParent());
+ fileOptions.set(BASE_DIR_CONTEXT, baseDir);
+
+ htmlRenderer = createHtmlRenderer(fileOptions);
+ fileSupport.putUserData(HTML_RENDERER_KEY, htmlRenderer);
+ file.putUserData(HTML_RENDERER_KEY, htmlRenderer);
+ return htmlRenderer;
}
/**
+ * This method is just used by Junit tests.
+ *
* Convert the given markdown
to Html.
*
* @param markdown the MarkDown content to convert to Html.
@@ -103,16 +148,17 @@ public NodeRenderer apply(DataHolder options) {
* @param fileName the file name which must be used to retrieve TextMate (if non-null) for MarkDown code block which defines the language or indented blockquote.
* @return the given markdown
to Html.
*/
- public @NotNull String toHtml(@NotNull String markdown, @Nullable Language language, @Nullable String fileName) {
+ public @NotNull String toHtml(@NotNull String markdown,
+ @Nullable Path baseDir,
+ @Nullable Language language,
+ @Nullable String fileName) {
var htmlRenderer = this.htmlRenderer;
if (language != null || fileName != null) {
- htmlRenderer = HtmlRenderer.builder(options)
- .nodeRendererFactory(new NodeRendererFactory() {
- @Override
- public NodeRenderer apply(DataHolder options) {
- return new SyntaxColorationCodeBlockRenderer(project, language, fileName);
- }
- }).build();
+ MutableDataSet fileOptions = new MutableDataSet(options);
+ fileOptions.set(BASE_DIR_CONTEXT, baseDir);
+ fileOptions.set(LANGUAGE_CONTEXT, language);
+ fileOptions.set(FILE_NAME_CONTEXT, fileName);
+ htmlRenderer = createHtmlRenderer(fileOptions);
}
return htmlRenderer.render(htmlParser.parse(markdown));
}
diff --git a/src/main/java/com/redhat/devtools/lsp4ij/features/documentation/markdown/LSPLinkResolver.java b/src/main/java/com/redhat/devtools/lsp4ij/features/documentation/markdown/LSPLinkResolver.java
new file mode 100644
index 000000000..71e298a85
--- /dev/null
+++ b/src/main/java/com/redhat/devtools/lsp4ij/features/documentation/markdown/LSPLinkResolver.java
@@ -0,0 +1,114 @@
+/*******************************************************************************
+ * Copyright (c) 2024 Red Hat, Inc.
+ * Distributed under license by Red Hat, Inc. All rights reserved.
+ * This program is made available under the terms of the
+ * Eclipse Public License v2.0 which accompanies this distribution,
+ * and is available at https://www.eclipse.org/legal/epl-v20.html
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and implementation
+ ******************************************************************************/
+package com.redhat.devtools.lsp4ij.features.documentation.markdown;
+
+import com.redhat.devtools.lsp4ij.LSPIJUtils;
+import com.redhat.devtools.lsp4ij.features.documentation.MarkdownConverter;
+import com.vladsch.flexmark.ast.Image;
+import com.vladsch.flexmark.ast.Link;
+import com.vladsch.flexmark.ast.Reference;
+import com.vladsch.flexmark.html.LinkResolver;
+import com.vladsch.flexmark.html.LinkResolverFactory;
+import com.vladsch.flexmark.html.renderer.LinkResolverBasicContext;
+import com.vladsch.flexmark.html.renderer.LinkStatus;
+import com.vladsch.flexmark.html.renderer.ResolvedLink;
+import com.vladsch.flexmark.util.ast.Node;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.File;
+import java.nio.file.Path;
+import java.util.Set;
+
+/**
+ * Custom link resolver used to resolve relative path by using the {@link com.intellij.psi.PsiFile}
+ * path which triggers the MarkDown converter for hover and completion documentation.
+ */
+public class LSPLinkResolver implements LinkResolver {
+
+ private final Path baseDir;
+
+ private enum FileUrlKind {
+ RELATIVE,
+ ABSOLUTE,
+ NONE;
+ }
+
+ public LSPLinkResolver(LinkResolverBasicContext context) {
+ this.baseDir = MarkdownConverter.BASE_DIR_CONTEXT.get(context.getOptions());
+ }
+
+ @Override
+ public @NotNull ResolvedLink resolveLink(@NotNull Node node, @NotNull LinkResolverBasicContext context, @NotNull ResolvedLink link) {
+ if (node instanceof Image || node instanceof Link || node instanceof Reference) {
+ String url = link.getUrl();
+ FileUrlKind fileUrlKind = getFileUrlKind(url);
+ if (baseDir != null && fileUrlKind == FileUrlKind.RELATIVE) {
+ String position = "";
+ int hashIndex= url.indexOf("#");
+ if (hashIndex != -1) {
+ position = url.substring(hashIndex, url.length());
+ url = url.substring(0, hashIndex);
+ }
+ try {
+ File resolvedFile = baseDir.resolve(url).toFile();
+ String resolvedUri = LSPIJUtils.toUri(resolvedFile).toASCIIString() + position;
+ return link.withStatus(LinkStatus.VALID)
+ .withUrl(resolvedUri);
+ }
+ catch(Exception e) {
+
+ }
+ }
+ }
+ return link;
+ }
+
+
+ private static FileUrlKind getFileUrlKind(String url) {
+ int index = url.indexOf("://");
+ if (index == -1) {
+ return FileUrlKind.RELATIVE;
+ }
+ if (url.substring(0, index).equals("file")) {
+ return FileUrlKind.ABSOLUTE;
+ }
+ return FileUrlKind.NONE;
+ }
+
+
+ public static class Factory implements LinkResolverFactory {
+
+ @Nullable
+ @Override
+ public Set> getAfterDependents() {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public Set> getBeforeDependents() {
+ return null;
+ }
+
+ @Override
+ public boolean affectsGlobalScope() {
+ return false;
+ }
+
+ @NotNull
+ @Override
+ public LinkResolver apply(@NotNull LinkResolverBasicContext context) {
+ return new LSPLinkResolver(context);
+ }
+ }
+
+}
diff --git a/src/main/java/com/redhat/devtools/lsp4ij/features/documentation/SyntaxColorationCodeBlockRenderer.java b/src/main/java/com/redhat/devtools/lsp4ij/features/documentation/markdown/SyntaxColorationCodeBlockRenderer.java
similarity index 87%
rename from src/main/java/com/redhat/devtools/lsp4ij/features/documentation/SyntaxColorationCodeBlockRenderer.java
rename to src/main/java/com/redhat/devtools/lsp4ij/features/documentation/markdown/SyntaxColorationCodeBlockRenderer.java
index 1f7e23221..800f67be7 100644
--- a/src/main/java/com/redhat/devtools/lsp4ij/features/documentation/SyntaxColorationCodeBlockRenderer.java
+++ b/src/main/java/com/redhat/devtools/lsp4ij/features/documentation/markdown/SyntaxColorationCodeBlockRenderer.java
@@ -8,7 +8,7 @@
* Contributors:
* Red Hat, Inc. - initial API and implementation
******************************************************************************/
-package com.redhat.devtools.lsp4ij.features.documentation;
+package com.redhat.devtools.lsp4ij.features.documentation.markdown;
import com.intellij.lang.Language;
import com.intellij.openapi.editor.highlighter.EditorHighlighter;
@@ -17,6 +17,8 @@
import com.intellij.openapi.editor.richcopy.SyntaxInfoBuilder;
import com.intellij.openapi.project.Project;
import com.intellij.psi.TokenType;
+import com.redhat.devtools.lsp4ij.features.documentation.LightQuickDocHighlightingHelper;
+import com.redhat.devtools.lsp4ij.features.documentation.MarkdownConverter;
import com.redhat.devtools.lsp4ij.internal.SimpleLanguageUtils;
import com.redhat.devtools.lsp4ij.internal.StringUtils;
import com.vladsch.flexmark.ast.FencedCodeBlock;
@@ -24,8 +26,10 @@
import com.vladsch.flexmark.html.HtmlWriter;
import com.vladsch.flexmark.html.renderer.NodeRenderer;
import com.vladsch.flexmark.html.renderer.NodeRendererContext;
+import com.vladsch.flexmark.html.renderer.NodeRendererFactory;
import com.vladsch.flexmark.html.renderer.NodeRenderingHandler;
import com.vladsch.flexmark.util.ast.ContentNode;
+import com.vladsch.flexmark.util.data.DataHolder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -54,10 +58,10 @@ public class SyntaxColorationCodeBlockRenderer implements NodeRenderer {
private final String fileName;
- public SyntaxColorationCodeBlockRenderer(Project project, Language fileLanguage, String fileName) {
- this.project = project;
- this.fileLanguage = fileLanguage;
- this.fileName = fileName;
+ public SyntaxColorationCodeBlockRenderer(DataHolder options) {
+ this.project = MarkdownConverter.PROJECT_CONTEXT.get(options);
+ this.fileLanguage = MarkdownConverter.LANGUAGE_CONTEXT.get(options);
+ this.fileName = MarkdownConverter.FILE_NAME_CONTEXT.get(options);
}
@Override
@@ -208,5 +212,11 @@ private static boolean hasTextMateSupport() {
}
}
+ public static class Factory implements NodeRendererFactory {
+ @Override
+ public NodeRenderer apply(DataHolder options) {
+ return new SyntaxColorationCodeBlockRenderer(options);
+ }
+ }
}
diff --git a/src/main/java/com/redhat/devtools/lsp4ij/features/documentation/TextMateHighlighterHelper.java b/src/main/java/com/redhat/devtools/lsp4ij/features/documentation/markdown/TextMateHighlighterHelper.java
similarity index 94%
rename from src/main/java/com/redhat/devtools/lsp4ij/features/documentation/TextMateHighlighterHelper.java
rename to src/main/java/com/redhat/devtools/lsp4ij/features/documentation/markdown/TextMateHighlighterHelper.java
index 94b83f4c0..6aa9733c5 100644
--- a/src/main/java/com/redhat/devtools/lsp4ij/features/documentation/TextMateHighlighterHelper.java
+++ b/src/main/java/com/redhat/devtools/lsp4ij/features/documentation/markdown/TextMateHighlighterHelper.java
@@ -8,7 +8,7 @@
* Contributors:
* Red Hat, Inc. - initial API and implementation
******************************************************************************/
-package com.redhat.devtools.lsp4ij.features.documentation;
+package com.redhat.devtools.lsp4ij.features.documentation.markdown;
import com.intellij.openapi.editor.colors.EditorColorsManager;
import com.intellij.openapi.editor.colors.EditorColorsScheme;
@@ -18,6 +18,7 @@
import com.intellij.openapi.fileTypes.SyntaxHighlighter;
import com.intellij.openapi.util.registry.Registry;
import com.redhat.devtools.lsp4ij.LanguageServersRegistry;
+import com.redhat.devtools.lsp4ij.features.documentation.markdown.SyntaxColorationCodeBlockRenderer;
import com.redhat.devtools.lsp4ij.internal.StringUtils;
import com.vladsch.flexmark.html.HtmlWriter;
import org.jetbrains.annotations.NotNull;
diff --git a/src/main/java/com/redhat/devtools/lsp4ij/hint/LSPNavigationLinkHandler.java b/src/main/java/com/redhat/devtools/lsp4ij/hint/LSPNavigationLinkHandler.java
index 51c1b801d..51921cfd5 100644
--- a/src/main/java/com/redhat/devtools/lsp4ij/hint/LSPNavigationLinkHandler.java
+++ b/src/main/java/com/redhat/devtools/lsp4ij/hint/LSPNavigationLinkHandler.java
@@ -20,49 +20,35 @@
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;
import org.jetbrains.annotations.NotNull;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import org.jetbrains.annotations.Nullable;
+
+import static com.redhat.devtools.lsp4ij.LSPIJUtils.HASH_SEPARATOR;
/**
* Handles tooltip links in format {@code #lsp-navigation/file_path:startLine;startChar;endLine;endChar}.
* On a click opens specified file in an editor and positions caret to the given offset.
*
*
- * This handler looks like {@link com.intellij.codeInsight.hint.NavigationLinkHandler} but as LSP works with position (line, character)
- * instead of offset, we provide this handler to avoid resolving offset from LSP position when
- * IntelliJ annotation is created with the tooltip.
+ * This handler looks like {@link com.intellij.codeInsight.hint.NavigationLinkHandler} but as LSP works with position (line, character)
+ * instead of offset, we provide this handler to avoid resolving offset from LSP position when
+ * IntelliJ annotation is created with the tooltip.
*
*/
-public class LSPNavigationLinkHandler extends TooltipLinkHandler {
-
- private static final Logger LOGGER = LoggerFactory.getLogger(LSPNavigationLinkHandler.class);//$NON-NLS-1$
+public class LSPNavigationLinkHandler extends TooltipLinkHandler {
private static final String PREFIX = "#lsp-navigation/";
- public static final String POS_SEPARATOR = ";";
@Override
- public boolean handleLink(@NotNull String refSuffix, @NotNull Editor editor) {
- int pos = refSuffix.lastIndexOf(':');
- if (pos <= 0 || pos == refSuffix.length() - 1) {
- LOGGER.info("Malformed suffix: " + refSuffix);
- return true;
- }
-
- String uri = refSuffix.substring(0, pos);
- Range range = toRange(refSuffix.substring(pos + 1));
- Location location = new Location();
- location.setUri(uri);
- if (range != null) {
- location.setRange(range);
- }
- return LSPIJUtils.openInEditor(location, editor.getProject());
+ public boolean handleLink(@NotNull String fileUrl,
+ @NotNull Editor editor) {
+ return LSPIJUtils.openInEditor(fileUrl, null, true, true, editor.getProject());
}
/**
* Returns the LSP navigation url from the given location.
*
*
- * {@code #lsp-navigation/file_path:startLine;startChar;endLine;endChar}
+ * {@code #lsp-navigation/file_path:startLine;startChar;endLine;endChar}
*
*
* @param location the LSP location.
@@ -71,39 +57,26 @@ public boolean handleLink(@NotNull String refSuffix, @NotNull Editor editor) {
public static String toNavigationUrl(@NotNull Location location) {
StringBuilder url = new StringBuilder(PREFIX);
url.append(location.getUri());
- url.append(":");
- if (location.getRange() != null) {
- toString(location.getRange(), url);
- }
+ appendStartPositionIfNeeded(location.getRange(), url);
return url.toString();
}
/**
* Serialize LSP range used in the LSP location Url.
- * @param range the LSP range.
+ *
+ * @param range the LSP range.
* @param result
*/
- private static void toString(@NotNull Range range, StringBuilder result) {
- result.append(range.getStart().getLine());
- result.append(POS_SEPARATOR);
- result.append(range.getStart().getCharacter());
- result.append(POS_SEPARATOR);
- result.append(range.getEnd().getLine());
- result.append(POS_SEPARATOR);
- result.append(range.getEnd().getCharacter());
- }
-
- private static Range toRange(String rangeString) {
- if (rangeString.isEmpty()) {
- return null;
+ private static void appendStartPositionIfNeeded(@Nullable Range range, StringBuilder result) {
+ Position start = range != null ? range.getStart() : null;
+ if (start == null) {
+ return;
}
- String[] positions = rangeString.split(POS_SEPARATOR);
- Position start = new Position(toInt(0, positions), toInt(1, positions));
- Position end = new Position(toInt(2, positions), toInt(3, positions));
- return new Range(start, end);
+ result.append(HASH_SEPARATOR);
+ result.append("L");
+ result.append(start.getLine());
+ result.append(":");
+ result.append(start.getCharacter());
}
- private static int toInt(int index, String[] positions) {
- return Integer.valueOf(positions[index]);
- }
}
diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml
index 41c2ddc77..97ae01b9c 100644
--- a/src/main/resources/META-INF/plugin.xml
+++ b/src/main/resources/META-INF/plugin.xml
@@ -216,6 +216,9 @@
id="LSPDocumentationTargetProvider"
implementation="com.redhat.devtools.lsp4ij.features.documentation.LSPDocumentationTargetProvider"
order="first"/>
+
""";
- assertEquals(html, toHtml(markdown, null, "test.ts"));
+ assertEquals(html, toHtml(markdown, null, null, "test.ts"));
}
public void testTypeScriptHighlightIndentedBlockquoteWithFileNameConversion() {
@@ -236,7 +238,7 @@ public void testTypeScriptHighlightIndentedBlockquoteWithFileNameConversion() {
""";
- assertEquals(html, toHtml(markdown, null, "test.ts"));
+ assertEquals(html, toHtml(markdown, null, null, "test.ts"));
}
public void testXmlHighlightIndentedBlockquoteWithLanguageConversion() {
@@ -255,14 +257,17 @@ public void testXmlHighlightIndentedBlockquoteWithLanguageConversion() {
""";
- assertEquals(html, toHtml(markdown, XMLLanguage.INSTANCE, null));
+ assertEquals(html, toHtml(markdown, null, XMLLanguage.INSTANCE, null));
}
private String toHtml(String markdown) {
return MarkdownConverter.getInstance(myFixture.getProject()).toHtml(markdown);
}
- private String toHtml(@NotNull String markdown, @Nullable Language language, @Nullable String fileName) {
- return MarkdownConverter.getInstance(myFixture.getProject()).toHtml(markdown,language, fileName);
+ private String toHtml(@NotNull String markdown,
+ @Nullable Path baseDir,
+ @Nullable Language language,
+ @Nullable String fileName) {
+ return MarkdownConverter.getInstance(myFixture.getProject()).toHtml(markdown, baseDir, language, fileName);
}
}
\ No newline at end of file
diff --git a/src/test/java/com/redhat/devtools/lsp4ij/features/documentation/MarkdownConverterWithPsiFileTest.java b/src/test/java/com/redhat/devtools/lsp4ij/features/documentation/MarkdownConverterWithPsiFileTest.java
new file mode 100644
index 000000000..07a4eba1a
--- /dev/null
+++ b/src/test/java/com/redhat/devtools/lsp4ij/features/documentation/MarkdownConverterWithPsiFileTest.java
@@ -0,0 +1,111 @@
+/*******************************************************************************
+ * Copyright (c) 2024 Red Hat, Inc.
+ * Distributed under license by Red Hat, Inc. All rights reserved.
+ * This program is made available under the terms of the
+ * Eclipse Public License v2.0 which accompanies this distribution,
+ * and is available at https://www.eclipse.org/legal/epl-v20.html
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and implementation
+ ******************************************************************************/
+package com.redhat.devtools.lsp4ij.features.documentation;
+
+import com.redhat.devtools.lsp4ij.LSPIJUtils;
+import com.redhat.devtools.lsp4ij.fixtures.LSPCodeInsightFixtureTestCase;
+
+import java.nio.file.Path;
+
+/**
+ * Test Markdown conversion to HTML by using PsiFile.
+ */
+public class MarkdownConverterWithPsiFileTest extends LSPCodeInsightFixtureTestCase {
+
+ private static final String USER_HOME = System.getProperty("user.home");
+
+ public void testHighlightCodeBlockConversion() {
+ // Here code block language is not set, the language is retrieved from the PsiFile.
+ String fileName = "test.txt";
+
+ String markdown = """
+ Here's some XML code:
+
+ ```
+
+
+ Angelo
+ Fred
+ Tests
+ I wrote them!
+
+ ```
+ """;
+
+ // As file is NOT an XML file, there are no syntax coloration
+ String html = """
+ Here's some XML code:
+ <?xml version="1.0" encoding="UTF-8"?>
+ <note>
+ <to>Angelo</to>
+ <from>Fred</from>
+ <heading>Tests</heading>
+ <body>I wrote them!</body>
+ </note>
+
+ """;
+
+ assertMarkdownConverter(fileName, markdown, html);
+ }
+
+ public void testXmlHighlightCodeBlockConversion() {
+ // Here code block language is not set, the language is retrieved from the PsiFile.
+ String fileName = "test.xml";
+
+ String markdown = """
+ Here's some XML code:
+
+ ```
+
+
+ Angelo
+ Fred
+ Tests
+ I wrote them!
+
+ ```
+ """;
+
+ // As file is an XML file, the XML syntax coloration is used.
+ String html = """
+ Here's some XML code:
+ <?xml version="1.0" encoding="UTF-8"?>
<note>
<to>Angelo</to>
<from>Fred</from>
<heading>Tests</heading>
<body>I wrote them!</body>
</note>
+ """;
+
+ assertMarkdownConverter(fileName, markdown, html);
+ }
+
+ public void testAbsoluteFileLink() {
+ String markdown = "[foo](file://" + USER_HOME + "/lsp/foo.txt)";
+ assertMarkdownConverter("bar.txt", markdown, "foo
\n");
+ }
+
+ public void testRelativeFileLink() {
+ String markdown = "[foo](lsp/foo.txt)";
+ assertMarkdownConverter("bar.txt", markdown, "foo
\n");
+ }
+
+ private void assertMarkdownConverter(String fileName, String markdown, String html) {
+ myFixture.configureByText(fileName, "");
+ var file = myFixture.getFile();
+
+ Path baseDir = file.getVirtualFile().getParent().getFileSystem().getNioPath(file.getVirtualFile().getParent());
+ String fileBaseDir = LSPIJUtils.toUri(baseDir.toFile()).toASCIIString();
+ fileBaseDir = fileBaseDir.substring("file://".length());
+ if (fileBaseDir.endsWith("/")) {
+ fileBaseDir = fileBaseDir.substring(0, fileBaseDir.length() - 1);
+ }
+ html = html.replace("$FILE_BASE_DIR$", fileBaseDir);
+ String actual = MarkdownConverter.getInstance(file.getProject()).toHtml(markdown, file);
+ assertEquals(html, actual);
+ }
+
+}
\ No newline at end of file