RUBY-21937 Added YAML simple copy-paste pre-processor

IDEA-CR-34731
This commit is contained in:
Alexey Merkulov
2018-07-23 14:53:14 +03:00
parent a68bf87f86
commit b9df542b72
15 changed files with 304 additions and 0 deletions
@@ -13,6 +13,7 @@
<lang.formatter language="yaml" implementationClass="org.jetbrains.yaml.formatter.YAMLFormattingModelBuilder"/>
<enterHandlerDelegate implementation="org.jetbrains.yaml.formatter.YAMLEnterAtIndentHandler" order="first"/>
<copyPastePreProcessor implementation="org.jetbrains.yaml.formatter.YAMLCopyPasteProcessor"/>
<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"/>
@@ -0,0 +1,100 @@
// 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 org.jetbrains.yaml.formatter;
import com.intellij.codeInsight.editorActions.CopyPastePreProcessor;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.*;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.LineTokenizer;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.yaml.YAMLLanguage;
import org.jetbrains.yaml.YAMLTextUtil;
import java.util.Iterator;
import java.util.List;
public class YAMLCopyPasteProcessor implements CopyPastePreProcessor {
@Nullable
@Override
public String preprocessOnCopy(PsiFile file, int[] startOffsets, int[] endOffsets, String text) {
return null;
}
@NotNull
@Override
public String preprocessOnPaste(Project project, PsiFile file, Editor editor, String text, RawText rawText) {
if (file.getLanguage() != YAMLLanguage.INSTANCE) return text;
CaretModel caretModel = editor.getCaretModel();
SelectionModel selectionModel = editor.getSelectionModel();
Document document = editor.getDocument();
int caretOffset = selectionModel.getSelectionStart() != selectionModel.getSelectionEnd() ?
selectionModel.getSelectionStart() : caretModel.getOffset();
int lineNumber = document.getLineNumber(caretOffset);
int lineStartOffset = YAMLTextUtil.getLineStartSafeOffset(document, lineNumber);
int indent = caretOffset - lineStartOffset;
if (indent == 0) {
// It could be copy and paste of lines
// User could fix indentation later if he wanted to copy some block into top-level block
return text;
}
return indentText(text, StringUtil.repeatSymbol(' ', indent));
}
@NotNull
private static String indentText(@NotNull String text, @NotNull String curLineIndent) {
List<String> lines = LineTokenizer.tokenizeIntoList(text, false, false);
if (lines.isEmpty()) {
// Such situation should not be possible
Logger.getInstance(YAMLCopyPasteProcessor.class).error(text.isEmpty()
? "Pasted empty text"
: "Text '" + text + "' was converted into empty line list");
return text;
}
int minIndent = calculateMinBlockIndent(lines);
String firstLine = lines.iterator().next();
if (lines.size() == 1) {
return firstLine;
}
return firstLine.substring(YAMLTextUtil.getStartIndentSize(firstLine)) + "\n" +
lines.stream().skip(1).map(line -> {
// remove common indent and add needed indent
if (isEmptyLine(line)) {
return curLineIndent + line.substring(minIndent);
}
else {
// do not indent empty lines at all
return "";
}
}).reduce((left, right) -> left + "\n" + right).orElse("");
}
private static int calculateMinBlockIndent(@NotNull List<String> list) {
Iterator<String> it = list.iterator();
String str = "";
while (it.hasNext()) {
str = it.next();
if (isEmptyLine(str)) {
break;
}
}
if (!it.hasNext()) {
return 0;
}
int minIndent = YAMLTextUtil.getStartIndentSize(str);
while (it.hasNext()) {
str = it.next();
if (isEmptyLine(str)) {
minIndent = Math.min(minIndent, YAMLTextUtil.getStartIndentSize(str));
}
}
return minIndent;
}
private static boolean isEmptyLine(@NotNull String str) {
return YAMLTextUtil.getStartIndentSize(str) < str.length();
}
}
@@ -0,0 +1,148 @@
// 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 org.jetbrains.yaml.paste;
import com.intellij.codeInsight.CodeInsightSettings;
import com.intellij.openapi.actionSystem.IdeActions;
import com.intellij.openapi.application.ex.PathManagerEx;
import com.intellij.openapi.ide.CopyPasteManager;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.testFramework.fixtures.LightPlatformCodeInsightFixtureTestCase;
import com.intellij.util.ObjectUtils;
import org.jetbrains.annotations.NotNull;
import java.awt.datatransfer.StringSelection;
public abstract class YAMLPasteTest extends LightPlatformCodeInsightFixtureTestCase {
private static final String ZERO_INDENT_SAMPLE = "key1:\n" +
" subKey: val1\n" +
"\n" +
"key2: val2";
private static final String INDENTED_SAMPLE = " key1:\n" +
" subKey: val1\n" +
"\n" +
" key2: val2";
private static final String EMPTY_LINES_SAMPLE = "\n" +
"\n" +
"\n";
private final int myReformatOnPaste;
private int myDefaultReformatOnPaste = CodeInsightSettings.INDENT_EACH_LINE;
@SuppressWarnings("JUnitTestCaseWithNonTrivialConstructors")
YAMLPasteTest(int paste) {
myReformatOnPaste = paste;
}
@Override
protected void setUp() throws Exception {
super.setUp();
myDefaultReformatOnPaste = CodeInsightSettings.getInstance().REFORMAT_ON_PASTE;
CodeInsightSettings.getInstance().REFORMAT_ON_PASTE = myReformatOnPaste;
}
@Override
protected void tearDown() throws Exception {
try {
CodeInsightSettings.getInstance().REFORMAT_ON_PASTE = myDefaultReformatOnPaste;
}
finally {
super.tearDown();
}
}
@Override
protected String getTestDataPath() {
return PathManagerEx.getCommunityHomePath() + "/plugins/yaml/testSrc/org/jetbrains/yaml/paste/data/";
}
public void testOneLinePaste() {
doTest(" middle");
}
public void testPasteValue_zeroIndent() {
doTest(ZERO_INDENT_SAMPLE);
}
public void testPasteValue_indented() {
doTest(INDENTED_SAMPLE);
}
public void testPasteValue_empty() {
doTest(EMPTY_LINES_SAMPLE, ".empty");
}
public void testPasteLines() {
doTest(INDENTED_SAMPLE);
}
public void testPasteItem_zeroIndent() {
doTest(ZERO_INDENT_SAMPLE);
}
public void testPasteItem_indented() {
doTest(INDENTED_SAMPLE);
}
public void testPasteItem_empty() {
doTest(EMPTY_LINES_SAMPLE, ".empty");
}
public void testPasteWithReplace_zeroIndent() {
doInsertTest(ZERO_INDENT_SAMPLE);
}
public void testPasteWithReplace_indented() {
doInsertTest(INDENTED_SAMPLE);
}
private void doTest(@NotNull String insert) {
doTest(insert, "");
}
private void doInsertTest(@NotNull String insert) {
doTest(insert, "", true);
}
private void doTest(@NotNull String insert, @NotNull String resultSuffix) {
doTest(insert, resultSuffix, false);
}
private void doTest(@NotNull String insert, @NotNull String resultSuffix, boolean selectWord) {
String testName = getTestName(true);
String fileName = ObjectUtils.notNull(StringUtil.substringBefore(testName, "_"), testName);
myFixture.configureByFile(fileName + ".yml");
CopyPasteManager.getInstance().setContents(new StringSelection(insert));
if (selectWord) {
myFixture.performEditorAction(IdeActions.ACTION_EDITOR_SELECT_WORD_AT_CARET);
}
myFixture.performEditorAction(IdeActions.ACTION_EDITOR_PASTE);
myFixture.checkResultByFile(fileName + resultSuffix + ".txt");
}
public static class YAMLPasteNoReformatTest extends YAMLPasteTest {
public YAMLPasteNoReformatTest() {
super(CodeInsightSettings.NO_REFORMAT);
}
}
public static class YAMLPasteIndentBlockTest extends YAMLPasteTest {
public YAMLPasteIndentBlockTest() {
super(CodeInsightSettings.INDENT_BLOCK);
}
}
public static class YAMLPasteIndentEachLineTest extends YAMLPasteTest {
public YAMLPasteIndentEachLineTest() {
super(CodeInsightSettings.INDENT_EACH_LINE);
}
}
public static class YAMLPasteReformatBlockTest extends YAMLPasteTest {
public YAMLPasteReformatBlockTest() {
super(CodeInsightSettings.REFORMAT_BLOCK);
}
}
}
@@ -0,0 +1,3 @@
top:
someKey1: start middle end
someKey2: value2
@@ -0,0 +1,3 @@
top:
someKey1: start<caret> end
someKey2: value2
@@ -0,0 +1,6 @@
top:
someKey:
# use double list for clear indent
- -
@@ -0,0 +1,7 @@
top:
someKey:
# use double list for clear indent
- - key1:
subKey: val1
key2: val2
@@ -0,0 +1,4 @@
top:
someKey:
# use double list for clear indent
- - <caret>
@@ -0,0 +1,6 @@
top:
someKey: value
key1:
subKey: val1
key2: val2
@@ -0,0 +1,3 @@
top:
someKey: value
<caret>
@@ -0,0 +1,5 @@
top:
someKey:
@@ -0,0 +1,6 @@
top:
someKey:
key1:
subKey: val1
key2: val2
@@ -0,0 +1,3 @@
top:
someKey:
<caret>
@@ -0,0 +1,6 @@
top:
someKey:
key1:
subKey: val1
key2: val2
@@ -0,0 +1,3 @@
top:
someKey:
some<caret>Word