mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-02-04 23:39:07 +07:00
IDEA-247565 Implement statement move in YAML
It supports now only block-style key-values and items IJ-CR-19054 GitOrigin-RevId: 200f3763895782a1eb55ee2907aee0fdb18b44ec
This commit is contained in:
committed by
intellij-monorepo-bot
parent
e0c488e837
commit
399b8ba853
@@ -22,6 +22,8 @@
|
||||
<typedHandler implementation="org.jetbrains.yaml.formatter.YAMLHyphenTypedHandler"/>
|
||||
<quoteHandler fileType="YAML" className="org.jetbrains.yaml.smart.YamlQuoteHandler"/>
|
||||
<copyPastePreProcessor implementation="org.jetbrains.yaml.smart.YAMLCopyPasteProcessor"/>
|
||||
<statementUpDownMover implementation="org.jetbrains.yaml.smart.YAMLStatementMover" id="yamlStatementMover"
|
||||
order="before line"/>
|
||||
<lang.parserDefinition language="yaml" implementationClass="org.jetbrains.yaml.YAMLParserDefinition"/>
|
||||
<lang.commenter language="yaml" implementationClass="org.jetbrains.yaml.YAMLCommenter"/>
|
||||
<lang.syntaxHighlighterFactory language="yaml" implementationClass="org.jetbrains.yaml.YAMLSyntaxHighlighterFactory"/>
|
||||
|
||||
110
plugins/yaml/src/org/jetbrains/yaml/smart/YAMLStatementMover.kt
Normal file
110
plugins/yaml/src/org/jetbrains/yaml/smart/YAMLStatementMover.kt
Normal file
@@ -0,0 +1,110 @@
|
||||
// Copyright 2000-2020 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 org.jetbrains.yaml.smart
|
||||
|
||||
import com.intellij.codeInsight.editorActions.moveUpDown.LineMover
|
||||
import com.intellij.codeInsight.editorActions.moveUpDown.LineRange
|
||||
import com.intellij.openapi.editor.Document
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.intellij.psi.util.PsiUtilCore
|
||||
import org.jetbrains.yaml.YAMLElementTypes
|
||||
import org.jetbrains.yaml.YAMLLanguage
|
||||
import org.jetbrains.yaml.psi.impl.YAMLBlockMappingImpl
|
||||
import org.jetbrains.yaml.psi.impl.YAMLBlockSequenceImpl
|
||||
|
||||
class YAMLStatementMover : LineMover() {
|
||||
override fun checkAvailable(editor: Editor, file: PsiFile, info: MoveInfo, down: Boolean): Boolean {
|
||||
if (!file.viewProvider.hasLanguage(YAMLLanguage.INSTANCE)) return false
|
||||
val offset = editor.caretModel.offset
|
||||
val selectionModel = editor.selectionModel
|
||||
val document = editor.document
|
||||
val lineNumber = document.getLineNumber(offset)
|
||||
val start: Int
|
||||
val end: Int
|
||||
if (selectionModel.hasSelection()) {
|
||||
start = selectionModel.selectionStart
|
||||
val selectionEnd = selectionModel.selectionEnd
|
||||
end = if (selectionEnd == 0) 0 else selectionEnd - 1
|
||||
}
|
||||
else {
|
||||
start = getLineStartSafeOffset(document, lineNumber)
|
||||
val lineEndOffset = document.getLineEndOffset(lineNumber)
|
||||
end = if (lineEndOffset == 0) 0 else lineEndOffset - 1
|
||||
}
|
||||
var elementToMove1 = findNextAtOffset(file, start, document) ?: return false
|
||||
var elementToMove2 = findPrevAtOffset(file, end, document) ?: return false
|
||||
if (PsiTreeUtil.isAncestor(elementToMove1, elementToMove2, false)) {
|
||||
elementToMove2 = elementToMove1
|
||||
}
|
||||
else if (PsiTreeUtil.isAncestor(elementToMove2, elementToMove1, false)) {
|
||||
elementToMove1 = elementToMove2
|
||||
}
|
||||
if (elementToMove2 !== elementToMove1) {
|
||||
val commonParent = PsiTreeUtil.findCommonParent(listOf(elementToMove1, elementToMove2))
|
||||
?: return false
|
||||
val moveScope = PsiTreeUtil.getNonStrictParentOfType(commonParent, YAMLBlockMappingImpl::class.java,
|
||||
YAMLBlockSequenceImpl::class.java)
|
||||
?: return false
|
||||
if (elementToMove1 !== moveScope) {
|
||||
while (elementToMove1.parent !== moveScope) {
|
||||
elementToMove1 = elementToMove1.parent ?: return false
|
||||
}
|
||||
}
|
||||
if (elementToMove2 !== moveScope) {
|
||||
while (elementToMove2.parent !== moveScope) {
|
||||
elementToMove2 = elementToMove2.parent ?: return false
|
||||
}
|
||||
}
|
||||
}
|
||||
val destination = getDestinationScope(file, (if (down) elementToMove2 else elementToMove1), down) ?: return false
|
||||
info.toMove = LineRange(elementToMove1, elementToMove2)
|
||||
info.toMove2 = destination
|
||||
info.indentTarget = false
|
||||
info.indentSource = false
|
||||
return true
|
||||
}
|
||||
|
||||
private fun findNextAtOffset(psiFile: PsiFile, beginAt: Int, document: Document): PsiElement? {
|
||||
var offset = beginAt
|
||||
while (offset < document.textLength) {
|
||||
if (!document.charsSequence[offset].isWhitespace()) {
|
||||
return psiFile.findElementAt(offset)
|
||||
}
|
||||
offset++
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun findPrevAtOffset(psiFile: PsiFile, beginAt: Int, document: Document): PsiElement? {
|
||||
var offset = beginAt
|
||||
while (offset >= 0) {
|
||||
if (!document.charsSequence[offset].isWhitespace()) {
|
||||
return psiFile.findElementAt(offset)
|
||||
}
|
||||
offset--
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun getDestinationScope(file: PsiFile, elementToMove: PsiElement, down: Boolean): LineRange? {
|
||||
val document = file.viewProvider.document ?: return null
|
||||
val offset = if (down) elementToMove.textRange.endOffset else elementToMove.textRange.startOffset
|
||||
val lineNumber = if (down) document.getLineNumber(offset) + 1 else document.getLineNumber(offset) - 1
|
||||
if (lineNumber < 0 || lineNumber >= document.lineCount) return null
|
||||
val destination = getDestinationElement(elementToMove, down) ?: return null
|
||||
val startLine = document.getLineNumber(destination.textRange.startOffset)
|
||||
val endLine = document.getLineNumber(destination.textRange.endOffset)
|
||||
return LineRange(startLine, endLine + 1)
|
||||
}
|
||||
|
||||
private fun getDestinationElement(elementToMove: PsiElement, down: Boolean): PsiElement? {
|
||||
var destination: PsiElement? = elementToMove
|
||||
do {
|
||||
destination = if (down) destination?.nextSibling else destination?.prevSibling
|
||||
}
|
||||
while (destination != null && YAMLElementTypes.SPACE_ELEMENTS.contains(PsiUtilCore.getElementType(destination)))
|
||||
return if (destination == elementToMove) null else destination
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package org.jetbrains.yaml.editing
|
||||
|
||||
import com.intellij.openapi.actionSystem.IdeActions
|
||||
import com.intellij.testFramework.fixtures.BasePlatformTestCase
|
||||
|
||||
class YAMLStatementMoverTest : BasePlatformTestCase() {
|
||||
fun testTopKeyValueUp() {
|
||||
myFixture.configureByText("test.yaml", """
|
||||
top1:
|
||||
child1: hi
|
||||
top2<caret>:
|
||||
child2: bye
|
||||
""".trimIndent())
|
||||
myFixture.performEditorAction(IdeActions.ACTION_MOVE_STATEMENT_UP_ACTION)
|
||||
//Note: move statement algorithm adds empty line to the end (if no one)
|
||||
myFixture.checkResult("""
|
||||
top2<caret>:
|
||||
child2: bye
|
||||
top1:
|
||||
child1: hi
|
||||
""".trimIndent() + "\n")
|
||||
}
|
||||
|
||||
fun testTopKeyValueDown() {
|
||||
myFixture.configureByText("test.yaml", """
|
||||
top1<caret>:
|
||||
child1: hi
|
||||
top2:
|
||||
child2: bye
|
||||
""".trimIndent())
|
||||
myFixture.performEditorAction(IdeActions.ACTION_MOVE_STATEMENT_DOWN_ACTION)
|
||||
//Note: move statement algorithm adds empty line to the end (if no one)
|
||||
myFixture.checkResult("""
|
||||
top2:
|
||||
child2: bye
|
||||
top1<caret>:
|
||||
child1: hi
|
||||
""".trimIndent() + "\n")
|
||||
}
|
||||
|
||||
fun testNonTopKeyValueDown() {
|
||||
myFixture.configureByText("test.yaml", """
|
||||
megatop:
|
||||
subtop1<caret>:
|
||||
child1: hi
|
||||
subtop2:
|
||||
child2: bye
|
||||
subtop3:
|
||||
child3: hello
|
||||
""".trimIndent())
|
||||
myFixture.performEditorAction(IdeActions.ACTION_MOVE_STATEMENT_DOWN_ACTION)
|
||||
myFixture.checkResult("""
|
||||
megatop:
|
||||
subtop2:
|
||||
child2: bye
|
||||
subtop1<caret>:
|
||||
child1: hi
|
||||
subtop3:
|
||||
child3: hello
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
fun testNonTopKeyValueUp() {
|
||||
myFixture.configureByText("test.yaml", """
|
||||
megatop:
|
||||
subtop1:
|
||||
child1: hi
|
||||
subtop2<caret>:
|
||||
child2: bye
|
||||
subtop3:
|
||||
child3: hello
|
||||
""".trimIndent())
|
||||
myFixture.performEditorAction(IdeActions.ACTION_MOVE_STATEMENT_UP_ACTION)
|
||||
myFixture.checkResult("""
|
||||
megatop:
|
||||
subtop2<caret>:
|
||||
child2: bye
|
||||
subtop1:
|
||||
child1: hi
|
||||
subtop3:
|
||||
child3: hello
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
fun testNonTopKeyValueDownWithSelection() {
|
||||
myFixture.configureByText("test.yaml", """
|
||||
megatop:
|
||||
<selection>subtop1:
|
||||
child1: hi
|
||||
subtop2:
|
||||
child2: bye</selection>
|
||||
subtop3:
|
||||
child3: hello
|
||||
""".trimIndent())
|
||||
myFixture.performEditorAction(IdeActions.ACTION_MOVE_STATEMENT_DOWN_ACTION)
|
||||
//Note: move statement algorithm adds empty line to the end (if no one)
|
||||
myFixture.checkResult("""
|
||||
megatop:
|
||||
subtop3:
|
||||
child3: hello
|
||||
<selection>subtop1:
|
||||
child1: hi
|
||||
subtop2:
|
||||
child2: bye</selection>
|
||||
""".trimIndent() + "\n")
|
||||
}
|
||||
|
||||
fun testNonTopKeyValueUpWithSelection() {
|
||||
myFixture.configureByText("test.yaml", """
|
||||
megatop:
|
||||
subtop1:
|
||||
child1: hi
|
||||
<selection>subtop2:
|
||||
child2: bye
|
||||
subtop3:
|
||||
child3: hello</selection>
|
||||
""".trimIndent())
|
||||
myFixture.performEditorAction(IdeActions.ACTION_MOVE_STATEMENT_UP_ACTION)
|
||||
//Note: move statement algorithm adds empty line to the end (if no one)
|
||||
myFixture.checkResult("""
|
||||
megatop:
|
||||
<selection>subtop2:
|
||||
child2: bye
|
||||
subtop3:
|
||||
child3: hello</selection>
|
||||
subtop1:
|
||||
child1: hi
|
||||
""".trimIndent() + "\n")
|
||||
}
|
||||
|
||||
fun testTopItemDown() {
|
||||
myFixture.configureByText("test.yaml", """
|
||||
<caret>- subkey1:
|
||||
value 1
|
||||
subkey2:
|
||||
value 2
|
||||
- item 2
|
||||
- item 3
|
||||
""".trimIndent())
|
||||
myFixture.performEditorAction(IdeActions.ACTION_MOVE_STATEMENT_DOWN_ACTION)
|
||||
myFixture.checkResult("""
|
||||
- item 2
|
||||
<caret>- subkey1:
|
||||
value 1
|
||||
subkey2:
|
||||
value 2
|
||||
- item 3
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
fun testTopItemUp() {
|
||||
myFixture.configureByText("test.yaml", """
|
||||
- subkey1:
|
||||
value 1
|
||||
subkey2:
|
||||
value 2
|
||||
- item 2<caret>
|
||||
- item 3
|
||||
""".trimIndent())
|
||||
myFixture.performEditorAction(IdeActions.ACTION_MOVE_STATEMENT_UP_ACTION)
|
||||
myFixture.checkResult("""
|
||||
- item 2<caret>
|
||||
- subkey1:
|
||||
value 1
|
||||
subkey2:
|
||||
value 2
|
||||
- item 3
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
fun testNonTopItemDown() {
|
||||
myFixture.configureByText("test.yaml", """
|
||||
top-array:
|
||||
<caret>- subkey1:
|
||||
value 1
|
||||
subkey2:
|
||||
value 2
|
||||
- subkey3:
|
||||
value 1
|
||||
subkey4:
|
||||
value 2
|
||||
- item 3
|
||||
""".trimIndent())
|
||||
myFixture.performEditorAction(IdeActions.ACTION_MOVE_STATEMENT_DOWN_ACTION)
|
||||
myFixture.checkResult("""
|
||||
top-array:
|
||||
- subkey3:
|
||||
value 1
|
||||
subkey4:
|
||||
value 2
|
||||
<caret>- subkey1:
|
||||
value 1
|
||||
subkey2:
|
||||
value 2
|
||||
- item 3
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
fun testNonTopItemUp() {
|
||||
myFixture.configureByText("test.yaml", """
|
||||
top-array:
|
||||
- subkey1:
|
||||
value 1
|
||||
subkey2:
|
||||
value 2
|
||||
<caret>- subkey3:
|
||||
value 1
|
||||
subkey4:
|
||||
value 2
|
||||
- item 3
|
||||
""".trimIndent())
|
||||
myFixture.performEditorAction(IdeActions.ACTION_MOVE_STATEMENT_UP_ACTION)
|
||||
myFixture.checkResult("""
|
||||
top-array:
|
||||
<caret>- subkey3:
|
||||
value 1
|
||||
subkey4:
|
||||
value 2
|
||||
- subkey1:
|
||||
value 1
|
||||
subkey2:
|
||||
value 2
|
||||
- item 3
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
fun testNonTopItemDownWithSelectionParts() {
|
||||
myFixture.configureByText("test.yaml", """
|
||||
top-array:
|
||||
- subkey1:
|
||||
value 1
|
||||
<selection>subkey2:
|
||||
value 2
|
||||
- subkey3:
|
||||
value 3</selection>
|
||||
subkey4:
|
||||
value 4
|
||||
- subkey5:
|
||||
value 5
|
||||
subkey6:
|
||||
value 6
|
||||
- item 4
|
||||
""".trimIndent())
|
||||
myFixture.performEditorAction(IdeActions.ACTION_MOVE_STATEMENT_DOWN_ACTION)
|
||||
myFixture.checkResult("""
|
||||
top-array:
|
||||
- subkey5:
|
||||
value 5
|
||||
subkey6:
|
||||
value 6
|
||||
- subkey1:
|
||||
value 1
|
||||
<selection>subkey2:
|
||||
value 2
|
||||
- subkey3:
|
||||
value 3</selection>
|
||||
subkey4:
|
||||
value 4
|
||||
- item 4
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
fun testMoveLastDown() {
|
||||
val fileContent = """
|
||||
top1: hi
|
||||
top2<caret>: bye
|
||||
|
||||
""".trimIndent()
|
||||
myFixture.configureByText("test.yaml", fileContent)
|
||||
myFixture.performEditorAction(IdeActions.ACTION_MOVE_STATEMENT_DOWN_ACTION)
|
||||
// Nothing should be changed
|
||||
myFixture.checkResult(fileContent)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user