mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IJPL-93440 Markdown: Create link from selection: do not create link in code snippets etc
GitOrigin-RevId: 4cf115e6079fdd241167ebbd1677cd8c56d5bdc9
This commit is contained in:
committed by
intellij-monorepo-bot
parent
7f0322abb8
commit
1b8403e5b9
+14
@@ -5,6 +5,9 @@ import com.intellij.lang.Language
|
||||
import com.intellij.notebooks.jupyter.core.jupyter.CellType
|
||||
import com.intellij.openapi.actionSystem.CommonDataKeys
|
||||
import com.intellij.openapi.actionSystem.DataContext
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.psi.PsiDocumentManager
|
||||
import com.intellij.psi.PsiFile
|
||||
import org.intellij.plugins.markdown.lang.MarkdownCompatibilityChecker
|
||||
|
||||
class JupyterMarkdownCompatibilityChecker : MarkdownCompatibilityChecker {
|
||||
@@ -18,4 +21,15 @@ class JupyterMarkdownCompatibilityChecker : MarkdownCompatibilityChecker {
|
||||
val cell = NotebookCellLines.get(editor).getCellByLineNumber(lineNumber) ?: return true
|
||||
return cell.type == CellType.MARKDOWN
|
||||
}
|
||||
|
||||
override fun isSupportedRange(file: PsiFile, range: TextRange): Boolean {
|
||||
if (!isSupportedLanguage(file.language) || range.isEmpty) return false
|
||||
val document = PsiDocumentManager.getInstance(file.project).getDocument(file) ?: return false
|
||||
val startLine = document.getLineNumber(range.startOffset)
|
||||
val endLine = document.getLineNumber(range.endOffset - 1)
|
||||
val cellLines = NotebookCellLines.get(document)
|
||||
val startCell = cellLines.getCellByLineNumber(startLine) ?: return false
|
||||
val endCell = cellLines.getCellByLineNumber(endLine) ?: return false
|
||||
return startCell == endCell && startCell.type == CellType.MARKDOWN
|
||||
}
|
||||
}
|
||||
+34
-3
@@ -5,7 +5,13 @@ import com.intellij.codeInsight.editorActions.CopyPastePreProcessor
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.editor.RawText
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.psi.util.elementType
|
||||
import com.intellij.psi.util.parents
|
||||
import org.intellij.plugins.markdown.lang.MarkdownElementTypes
|
||||
import org.intellij.plugins.markdown.lang.MarkdownLanguage
|
||||
import org.intellij.plugins.markdown.lang.MarkdownTokenTypes
|
||||
import org.intellij.plugins.markdown.lang.supportsMarkdown
|
||||
|
||||
internal class MarkdownLinkPastePreProcessor : CopyPastePreProcessor {
|
||||
@@ -16,7 +22,8 @@ internal class MarkdownLinkPastePreProcessor : CopyPastePreProcessor {
|
||||
override fun preprocessOnPaste(project: Project, file: PsiFile, editor: Editor, text: String, rawText: RawText?): String {
|
||||
val linkText = editor.selectionModel.selectedText ?: return text
|
||||
val linkDestination = MarkdownLinkEditingUtil.getLinkDestination(text) ?: return text
|
||||
if (linkText.isBlank() || !file.language.supportsMarkdown()) {
|
||||
val selectionRange = TextRange(editor.selectionModel.selectionStart, editor.selectionModel.selectionEnd)
|
||||
if (linkText.isBlank() || !file.supportsMarkdown(selectionRange) || isInIgnoredContext(file, selectionRange)) {
|
||||
return text
|
||||
}
|
||||
return MarkdownLinkEditingUtil.createInlineLink(linkText, linkDestination)
|
||||
@@ -26,7 +33,31 @@ internal class MarkdownLinkPastePreProcessor : CopyPastePreProcessor {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun requiresAllDocumentsToBeCommitted(editor: Editor, project: Project): Boolean {
|
||||
return false
|
||||
private fun isInIgnoredContext(file: PsiFile, selectionRange: TextRange): Boolean {
|
||||
val contextFile = file.viewProvider.getPsi(MarkdownLanguage.INSTANCE) ?: file
|
||||
val startElement = contextFile.findElementAt(selectionRange.startOffset) ?: return false
|
||||
val endElement = contextFile.findElementAt(selectionRange.endOffset - 1) ?: return false
|
||||
return sequenceOf(startElement, endElement).any { element ->
|
||||
// Markdown PSI can return an embedded HTML element at the requested offset.
|
||||
!element.supportsMarkdown() || element.parents(withSelf = true).any { it.elementType in ignoredLinkPasteContextTypes }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val ignoredLinkPasteContextTypes = setOf(
|
||||
MarkdownElementTypes.CODE_FENCE,
|
||||
MarkdownElementTypes.CODE_BLOCK,
|
||||
MarkdownElementTypes.CODE_SPAN,
|
||||
MarkdownElementTypes.HTML_BLOCK,
|
||||
MarkdownElementTypes.LINK_TEXT,
|
||||
MarkdownElementTypes.LINK_DESTINATION,
|
||||
MarkdownElementTypes.LINK_DEFINITION,
|
||||
MarkdownElementTypes.INLINE_LINK,
|
||||
MarkdownElementTypes.FULL_REFERENCE_LINK,
|
||||
MarkdownElementTypes.SHORT_REFERENCE_LINK,
|
||||
MarkdownElementTypes.IMAGE,
|
||||
MarkdownElementTypes.TEST_LINK,
|
||||
MarkdownElementTypes.AUTOLINK,
|
||||
MarkdownElementTypes.FRONT_MATTER_HEADER,
|
||||
MarkdownTokenTypes.GFM_AUTOLINK,
|
||||
)
|
||||
|
||||
+11
@@ -4,6 +4,9 @@ package org.intellij.plugins.markdown.lang
|
||||
import com.intellij.lang.Language
|
||||
import com.intellij.openapi.actionSystem.DataContext
|
||||
import com.intellij.openapi.extensions.ExtensionPointName
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiFile
|
||||
|
||||
/**
|
||||
* Provides support for languages that either are Markdown or are compatible with Markdown-like features.
|
||||
@@ -20,6 +23,14 @@ interface MarkdownCompatibilityChecker {
|
||||
}
|
||||
|
||||
fun isSupportedLanguage(language: Language): Boolean
|
||||
|
||||
fun isSupportedElement(element: PsiElement): Boolean {
|
||||
return isSupportedLanguage(element.containingFile.language)
|
||||
}
|
||||
|
||||
fun isSupportedRange(file: PsiFile, range: TextRange): Boolean {
|
||||
return isSupportedLanguage(file.language)
|
||||
}
|
||||
|
||||
fun isSupportedContext(language: Language, dataContext: DataContext?): Boolean {
|
||||
return isSupportedLanguage(language)
|
||||
|
||||
@@ -4,7 +4,10 @@ import com.intellij.lang.Language
|
||||
import com.intellij.openapi.actionSystem.DataContext
|
||||
import com.intellij.openapi.fileTypes.FileType
|
||||
import com.intellij.openapi.fileTypes.FileTypeRegistry
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiFile
|
||||
|
||||
fun Language.isMarkdownLanguage(): Boolean {
|
||||
return this == MarkdownLanguage.INSTANCE
|
||||
@@ -14,6 +17,14 @@ fun Language.supportsMarkdown(dataContext: DataContext? = null): Boolean {
|
||||
return MarkdownCompatibilityChecker.EP_NAME.extensionList.any { it.isSupportedContext(this, dataContext) }
|
||||
}
|
||||
|
||||
fun PsiElement.supportsMarkdown(): Boolean {
|
||||
return MarkdownCompatibilityChecker.EP_NAME.extensionList.any { it.isSupportedElement(this) }
|
||||
}
|
||||
|
||||
fun PsiFile.supportsMarkdown(range: TextRange): Boolean {
|
||||
return MarkdownCompatibilityChecker.EP_NAME.extensionList.any { it.isSupportedRange(this, range) }
|
||||
}
|
||||
|
||||
fun FileType.isMarkdownType(): Boolean {
|
||||
return this == MarkdownFileType.INSTANCE
|
||||
}
|
||||
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package org.intellij.plugins.markdown.editor
|
||||
|
||||
import com.intellij.codeInsight.editorActions.CopyPastePreProcessor
|
||||
import com.intellij.testFramework.fixtures.LightPlatformCodeInsightFixture4TestCase
|
||||
import org.junit.Test
|
||||
|
||||
class MarkdownLinkPastePreProcessorTest : LightPlatformCodeInsightFixture4TestCase() {
|
||||
@Test
|
||||
fun `creates link in Markdown text`() {
|
||||
assertPreprocessedText("Some <selection>text</selection>", "[text](https://example.com)")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `creates link in Markdown formatting contexts`() {
|
||||
assertPreprocessedText("- *<selection>list item</selection>*", "[list item](https://example.com)")
|
||||
assertPreprocessedText("| Column |\n| --- |\n| <selection>table cell</selection> |", "[table cell](https://example.com)")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `does not create link in fenced code block`() {
|
||||
assertPreprocessedText("""
|
||||
```
|
||||
<selection>some code</selection>
|
||||
```
|
||||
""".trimIndent(), "https://example.com")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `does not create link in indented code block`() {
|
||||
assertPreprocessedText(" <selection>some code</selection>", "https://example.com")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `does not create link in inline code`() {
|
||||
assertPreprocessedText("`<selection>some code</selection>`", "https://example.com")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `does not create nested link`() {
|
||||
assertPreprocessedText("[<selection>link text</selection>](https://old.example.com)", "https://example.com")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `does not create link from an existing link`() {
|
||||
assertPreprocessedText("<selection>[link](https://old.example.com)</selection>", "https://example.com")
|
||||
assertPreprocessedText("<selection>[link][label]</selection>\n\n[label]: https://old.example.com", "https://example.com")
|
||||
assertPreprocessedText("<selection>[label]</selection>\n\n[label]: https://old.example.com", "https://example.com")
|
||||
assertPreprocessedText("[link](<selection>https://old.example.com</selection>)", "https://example.com")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `does not create link in autolink`() {
|
||||
assertPreprocessedText("<selection>https://old.example.com</selection>", "https://example.com")
|
||||
assertPreprocessedText("<<selection>https://old.example.com</selection>>", "https://example.com")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `does not create link in HTML block`() {
|
||||
assertPreprocessedText("<div>\n<selection>some text</selection>\n</div>", "https://example.com")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `does not create link in front matter`() {
|
||||
assertPreprocessedText("---\ntitle: <selection>some text</selection>\n---", "https://example.com")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `does not create link from blank selection`() {
|
||||
assertPreprocessedText("Some <selection> </selection> text", "https://example.com")
|
||||
}
|
||||
|
||||
private fun assertPreprocessedText(fileText: String, expected: String) {
|
||||
myFixture.configureByText("test.md", fileText)
|
||||
val processor = CopyPastePreProcessor.EP_NAME.extensionList.single {
|
||||
it.javaClass.name == "org.intellij.plugins.markdown.editor.MarkdownLinkPastePreProcessor"
|
||||
}
|
||||
val actual = processor.preprocessOnPaste(
|
||||
project,
|
||||
myFixture.file,
|
||||
myFixture.editor,
|
||||
"https://example.com",
|
||||
null,
|
||||
)
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user