You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@netbeans.apache.org by jl...@apache.org on 2019/08/25 08:45:01 UTC
[netbeans] branch master updated: JDK13 text block support
This is an automated email from the ASF dual-hosted git repository.
jlahoda pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/netbeans.git
The following commit(s) were added to refs/heads/master by this push:
new cb50c2f JDK13 text block support
cb50c2f is described below
commit cb50c2fede0387535900727e1abdcd4718ab02fd
Author: Reema Taneja <32...@users.noreply.github.com>
AuthorDate: Sun Aug 25 14:14:54 2019 +0530
JDK13 text block support
---
ide/editor.indent/apichanges.xml | 12 ++
.../modules/editor/indent/spi/Context.java | 32 ++++-
.../src/org/netbeans/editor/BaseKit.java | 51 +++++--
.../editor/base/semantic/ColoringAttributes.java | 4 +-
.../base/semantic/SemanticHighlighterBase.java | 59 +++++++-
.../java/editor/base/semantic/TokenList.java | 24 ++++
.../java/editor/base/semantic/DetectorTest.java | 43 ++++++
.../java/editor/base/semantic/HighlightImpl.java | 1 +
.../java/editor/base/semantic/MarkOccDetTest.java | 17 ++-
.../java/editor/base/semantic/TestBase.java | 80 ++++++++++-
.../modules/editor/java/TypingCompletion.java | 53 +++++--
.../java/editor/imports/ClipboardHandler.java | 128 ++++++++++++-----
.../modules/java/editor/resources/fontsColors.xml | 1 +
.../java/editor/semantic/ColoringManager.java | 1 +
.../java/editor/semantic/SemanticHighlighter.java | 8 +-
.../editor/java/TypingCompletionUnitTest.java | 63 ++++++++-
.../java/editor/imports/ClipboardHandlerTest.java | 15 ++
.../modules/java/hints/jdk/ConvertToTextBlock.java | 109 +++++++++++++++
.../modules/java/hints/resources/jdk13.properties | 17 +++
.../modules/java/hints/resources/layer.xml | 16 +++
.../java/hints/jdk/ConvertToTextBlockTest.java | 155 +++++++++++++++++++++
java/java.lexer/apichanges.xml | 12 ++
.../org/netbeans/api/java/lexer/JavaTokenId.java | 2 +
.../src/org/netbeans/lib/java/lexer/JavaLexer.java | 24 +++-
.../lib/java/lexer/JavaLexerBatchTest.java | 35 ++++-
.../org/netbeans/api/java/source/SourceUtils.java | 26 +++-
.../modules/java/source/builder/TreeFactory.java | 2 +
.../modules/java/source/pretty/CharBuffer.java | 16 ++-
.../modules/java/source/pretty/VeryPretty.java | 34 ++++-
.../modules/java/source/save/Reindenter.java | 34 ++++-
.../netbeans/api/java/source/gen/LiteralTest.java | 88 ++++++++++++
.../modules/java/source/save/ReindenterTest.java | 35 ++++-
32 files changed, 1104 insertions(+), 93 deletions(-)
diff --git a/ide/editor.indent/apichanges.xml b/ide/editor.indent/apichanges.xml
index 932020a..255737d 100644
--- a/ide/editor.indent/apichanges.xml
+++ b/ide/editor.indent/apichanges.xml
@@ -83,6 +83,18 @@ is the proper place.
<!-- ACTUAL CHANGES BEGIN HERE: -->
<changes>
+ <change id="indent.given.indent">
+ <summary>Indent can be given as a string</summary>
+ <version major="1" minor="49"/>
+ <date day="16" month="6" year="2019"/>
+ <author login="jlahoda"/>
+ <compatibility source="compatible" binary="compatible"/>
+ <description>
+ Added method org.netbeans.modules.editor.indent.spi.Context.modifyIndent(int, int, String),
+ which allows to set indent by specifying particular indent string.
+ </description>
+ <class package="org.netbeans.modules.editor.indent.spi" name="Context"/>
+ </change>
<change id="indent.support">
<summary>Indentation Support module created</summary>
<version major="1" minor="39"/>
diff --git a/ide/editor.indent/src/org/netbeans/modules/editor/indent/spi/Context.java b/ide/editor.indent/src/org/netbeans/modules/editor/indent/spi/Context.java
index a9d1108..0ed70ad 100644
--- a/ide/editor.indent/src/org/netbeans/modules/editor/indent/spi/Context.java
+++ b/ide/editor.indent/src/org/netbeans/modules/editor/indent/spi/Context.java
@@ -170,23 +170,43 @@ public final class Context {
}
String newIndentString = IndentUtils.createIndentString(doc, newIndent);
+
+ modifyIndent(lineStartOffset, oldIndentEndOffset - lineStartOffset, newIndentString);
+ }
+
+ /**
+ * Modify indent of the line at the offset passed as the parameter, by stripping the given
+ * number of input characters and inserting the given indent.
+ *
+ * @param lineStartOffset start offset of a line where the indent is being modified.
+ * @param oldIndentCharCount number of characters to remove.
+ * @param newIndent new indent.
+ * @throws javax.swing.text.BadLocationException if the given lineStartOffset is not within
+ * corresponding document's bounds.
+ * @since 1.49
+ */
+ public void modifyIndent(int lineStartOffset, int oldIndentCharCount, String newIndent) throws BadLocationException {
+ Document doc = document();
+ IndentImpl.checkOffsetInDocument(doc, lineStartOffset);
+ CharSequence docText = DocumentUtilities.getText(doc);
+ int oldIndentEndOffset = lineStartOffset + oldIndentCharCount;
// Attempt to match the begining characters
int offset = lineStartOffset;
- for (int i = 0; i < newIndentString.length() && lineStartOffset + i < oldIndentEndOffset; i++) {
- if (newIndentString.charAt(i) != docText.charAt(lineStartOffset + i)) {
+ for (int i = 0; i < newIndent.length() && lineStartOffset + i < oldIndentEndOffset; i++) {
+ if (newIndent.charAt(i) != docText.charAt(lineStartOffset + i)) {
offset = lineStartOffset + i;
- newIndentString = newIndentString.substring(i);
+ newIndent = newIndent.substring(i);
break;
}
}
// Replace the old indent
- if (!doc.getText(offset, oldIndentEndOffset - offset).equals(newIndentString)) {
+ if (!doc.getText(offset, oldIndentEndOffset - offset).equals(newIndent)) {
if (offset < oldIndentEndOffset) {
doc.remove(offset, oldIndentEndOffset - offset);
}
- if (newIndentString.length() > 0) {
- doc.insertString(offset, newIndentString, null);
+ if (newIndent.length() > 0) {
+ doc.insertString(offset, newIndent, null);
}
}
}
diff --git a/ide/editor.lib/src/org/netbeans/editor/BaseKit.java b/ide/editor.lib/src/org/netbeans/editor/BaseKit.java
index b36c6c9..0add683 100644
--- a/ide/editor.lib/src/org/netbeans/editor/BaseKit.java
+++ b/ide/editor.lib/src/org/netbeans/editor/BaseKit.java
@@ -1374,25 +1374,48 @@ public class BaseKit extends DefaultEditorKit {
editorUI.getWordMatch().clear(); // reset word matching
Boolean overwriteMode = (Boolean)editorUI.getProperty(EditorUI.OVERWRITE_MODE_PROPERTY);
boolean ovr = (overwriteMode != null && overwriteMode.booleanValue());
- if (Utilities.isSelectionShowing(caret)) { // valid selection
- try {
- doc.putProperty(DOC_REPLACE_SELECTION_PROPERTY, true);
- replaceSelection(target, insertionOffset, caret, insertionText, ovr);
- } finally {
- doc.putProperty(DOC_REPLACE_SELECTION_PROPERTY, null);
+ int currentInsertOffset = insertionOffset;
+ int targetCaretOffset = caretPosition;
+ for (int i = 0; i < insertionText.length();) {
+ int end = insertionText.indexOf('\n', i);
+ if (end == (-1)) end = insertionText.length();
+ String currentLine = insertionText.substring(i, end);
+ if (i == 0) {
+ if (Utilities.isSelectionShowing(caret)) { // valid selection
+ try {
+ doc.putProperty(DOC_REPLACE_SELECTION_PROPERTY, true);
+ replaceSelection(target, currentInsertOffset, caret, currentLine, ovr);
+ } finally {
+ doc.putProperty(DOC_REPLACE_SELECTION_PROPERTY, null);
+ }
+ } else { // no selection
+ if (ovr && currentInsertOffset < doc.getLength() && doc.getChars(currentInsertOffset, 1)[0] != '\n') { //NOI18N
+ // overwrite current char
+ insertString(doc, currentInsertOffset, caret, currentLine, true);
+ } else { // insert mode
+ insertString(doc, currentInsertOffset, caret, currentLine, false);
+ }
+ }
+ } else {
+ Indent indent = Indent.get(doc);
+ indent.lock();
+ try {
+ currentInsertOffset = indent.indentNewLine(currentInsertOffset);
+ } finally {
+ indent.unlock();
+ }
+ insertString(doc, currentInsertOffset, caret, currentLine, false);
}
- } else { // no selection
- if (ovr && insertionOffset < doc.getLength() && doc.getChars(insertionOffset, 1)[0] != '\n') { //NOI18N
- // overwrite current char
- insertString(doc, insertionOffset, caret, insertionText, true);
- } else { // insert mode
- insertString(doc, insertionOffset, caret, insertionText, false);
+ if (caretPosition >= i && caretPosition <= end) {
+ targetCaretOffset = currentInsertOffset - insertionOffset + caretPosition - i;
}
+ currentInsertOffset += currentLine.length();
+ i = end + 1;
}
- if (caretPosition != -1) {
+ if (targetCaretOffset != -1) {
assert caretPosition >= 0 && (caretPosition <= insertionText.length());
- caret.setDot(insertionOffset + caretPosition);
+ caret.setDot(insertionOffset + targetCaretOffset);
}
} finally {
DocumentUtilities.setTypingModification(doc, false);
diff --git a/java/java.editor.base/src/org/netbeans/modules/java/editor/base/semantic/ColoringAttributes.java b/java/java.editor.base/src/org/netbeans/modules/java/editor/base/semantic/ColoringAttributes.java
index b9796f7..9f09f17 100644
--- a/java/java.editor.base/src/org/netbeans/modules/java/editor/base/semantic/ColoringAttributes.java
+++ b/java/java.editor.base/src/org/netbeans/modules/java/editor/base/semantic/ColoringAttributes.java
@@ -62,7 +62,9 @@ public enum ColoringAttributes {
KEYWORD,
- JAVADOC_IDENTIFIER;
+ JAVADOC_IDENTIFIER,
+
+ UNINDENTED_TEXT_BLOCK;
public static Coloring empty() {
return new Coloring();
diff --git a/java/java.editor.base/src/org/netbeans/modules/java/editor/base/semantic/SemanticHighlighterBase.java b/java/java.editor.base/src/org/netbeans/modules/java/editor/base/semantic/SemanticHighlighterBase.java
index 58c7a05..5d6d51c 100644
--- a/java/java.editor.base/src/org/netbeans/modules/java/editor/base/semantic/SemanticHighlighterBase.java
+++ b/java/java.editor.base/src/org/netbeans/modules/java/editor/base/semantic/SemanticHighlighterBase.java
@@ -65,12 +65,14 @@ import javax.lang.model.element.Modifier;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.swing.text.Document;
+import org.netbeans.api.java.lexer.JavaTokenId;
import org.netbeans.api.java.source.CompilationInfo;
import org.netbeans.api.java.source.ElementHandle;
import org.netbeans.api.java.source.JavaParserResultTask;
import org.netbeans.api.java.source.JavaSource.Phase;
import org.netbeans.api.java.source.TreeUtilities;
import org.netbeans.api.java.source.support.CancellableTreePathScanner;
+import org.netbeans.api.lexer.PartType;
import org.netbeans.api.lexer.Token;
import org.netbeans.api.lexer.TokenHierarchy;
//import org.netbeans.modules.editor.NbEditorUtilities;
@@ -81,6 +83,8 @@ import org.netbeans.modules.parsing.spi.Scheduler;
import org.netbeans.modules.parsing.spi.SchedulerEvent;
import org.netbeans.modules.parsing.spi.TaskIndexingMode;
import org.openide.filesystems.FileUtil;
+import org.openide.util.Exceptions;
+import org.openide.util.Pair;
/**
@@ -249,23 +253,25 @@ public abstract class SemanticHighlighterBase extends JavaParserResultTask {
boolean computeUnusedImports = "text/x-java".equals(FileUtil.getMIMEType(info.getFileObject()));
- List<int[]> imports = computeUnusedImports ? new ArrayList<int[]>() : null;
+ List<Pair<int[], Coloring>> extraColoring = computeUnusedImports ? new ArrayList<>(v.extraColoring) : v.extraColoring;
if (computeUnusedImports) {
Collection<TreePath> unusedImports = UnusedImports.process(info, cancel);
if (unusedImports == null) return true;
+ Coloring unused = collection2Coloring(Arrays.asList(ColoringAttributes.UNUSED));
+
for (TreePath tree : unusedImports) {
if (cancel.get()) {
return true;
}
//XXX: finish
- imports.add(new int[] {
+ extraColoring.add(Pair.of(new int[] {
(int) info.getTrees().getSourcePositions().getStartPosition(cu, tree.getLeaf()),
(int) info.getTrees().getSourcePositions().getEndPosition(cu, tree.getLeaf())
- });
+ }, unused));
}
}
@@ -320,7 +326,7 @@ public abstract class SemanticHighlighterBase extends JavaParserResultTask {
return true;
if (computeUnusedImports) {
- setter.setHighlights(doc, imports, v.preText);
+ setter.setHighlights(doc, extraColoring, v.preText);
}
setter.setColorings(doc, newColoring);
@@ -407,6 +413,7 @@ public abstract class SemanticHighlighterBase extends JavaParserResultTask {
private Map<Element, List<Use>> type2Uses;
private Map<Tree, List<Token>> tree2Tokens;
private List<Token> contextKeywords;
+ private List<Pair<int[], Coloring>> extraColoring;
private Map<int[], String> preText;
private TokenList tl;
private long memberSelectBypass = -1;
@@ -421,6 +428,7 @@ public abstract class SemanticHighlighterBase extends JavaParserResultTask {
type2Uses = new HashMap<Element, List<Use>>();
tree2Tokens = new IdentityHashMap<Tree, List<Token>>();
contextKeywords = new ArrayList<>();
+ extraColoring = new ArrayList<>();
preText = new HashMap<>();
tl = new TokenList(info, doc, cancel);
@@ -1063,8 +1071,36 @@ public abstract class SemanticHighlighterBase extends JavaParserResultTask {
return null;
}
+ private static final Coloring UNINDENTED_TEXT_BLOCK =
+ ColoringAttributes.add(ColoringAttributes.empty(), ColoringAttributes.UNINDENTED_TEXT_BLOCK);
+
@Override
public Void visitLiteral(LiteralTree node, Void p) {
+ int startPos = (int) info.getTrees().getSourcePositions().getStartPosition(info.getCompilationUnit(), node);
+ tl.moveToOffset(startPos);
+ Token t = tl.currentToken();
+ if (t != null && t.id() == JavaTokenId.MULTILINE_STRING_LITERAL && t.partType() == PartType.COMPLETE) {
+ String tokenText = t.text().toString();
+ String[] lines = tokenText.split("\n");
+ int indent = Arrays.stream(lines, 1, lines.length)
+ .mapToInt(this::leadingIndent)
+ .min()
+ .orElse(0);
+ int pos = startPos + lines[0].length() + 1;
+ for (int i = 1; i < lines.length; i++) {
+ String line = lines[i];
+ if (i == lines.length - 1) {
+ line = line.substring(0, line.length() - 3);
+ }
+ String strippendLine = line.replaceAll("[\t ]+$", "");
+ int indentedStart = pos + indent;
+ int indentedEnd = pos + strippendLine.length();
+ if (indentedEnd > indentedStart)
+ extraColoring.add(Pair.of(new int[] {indentedStart, indentedEnd}, UNINDENTED_TEXT_BLOCK));
+ pos += line.length() + 1;
+ }
+ }
+
TreePath pp = getCurrentPath().getParentPath();
if (pp.getLeaf() != null &&
pp.getLeaf().getKind() == Kind.METHOD_INVOCATION) {
@@ -1085,11 +1121,24 @@ public abstract class SemanticHighlighterBase extends JavaParserResultTask {
return super.visitLiteral(node, p);
}
+ private int leadingIndent(String line) {
+ int indent = 0;
+
+ for (int i = 0; i < line.length(); i++) { //TODO: code points
+ if (Character.isWhitespace(line.charAt(i)))
+ indent++;
+ else
+ break;
+ }
+
+ return indent;
+ }
+
}
public static interface ErrorDescriptionSetter {
- public void setHighlights(Document doc, Collection<int[]> highlights, Map<int[], String> preText);
+ public void setHighlights(Document doc, Collection<Pair<int[], Coloring>> highlights, Map<int[], String> preText);
public void setColorings(Document doc, Map<Token, Coloring> colorings);
}
}
diff --git a/java/java.editor.base/src/org/netbeans/modules/java/editor/base/semantic/TokenList.java b/java/java.editor.base/src/org/netbeans/modules/java/editor/base/semantic/TokenList.java
index 1bab7c3..cadc339 100644
--- a/java/java.editor.base/src/org/netbeans/modules/java/editor/base/semantic/TokenList.java
+++ b/java/java.editor.base/src/org/netbeans/modules/java/editor/base/semantic/TokenList.java
@@ -240,6 +240,30 @@ public class TokenList {
});
}
+ public Token currentToken() {
+ Token[] res = new Token[1];
+ doc.render(new Runnable() {
+ @Override
+ public void run() {
+ if (cancel.get()) {
+ return ;
+ }
+
+ if (ts != null && !ts.isValid()) {
+ cancel.set(true);
+ return ;
+ }
+
+ if (ts == null) {
+ return ;
+ }
+
+ res[0] = ts.token();
+ }
+ });
+ return res[0];
+ }
+
public void moduleNameHere(final ExpressionTree tree, final Map<Tree, List<Token>> tree2Tokens) {
doc.render(new Runnable() {
@Override
diff --git a/java/java.editor.base/test/unit/src/org/netbeans/modules/java/editor/base/semantic/DetectorTest.java b/java/java.editor.base/test/unit/src/org/netbeans/modules/java/editor/base/semantic/DetectorTest.java
index 1f25ff4..4dcece3 100644
--- a/java/java.editor.base/test/unit/src/org/netbeans/modules/java/editor/base/semantic/DetectorTest.java
+++ b/java/java.editor.base/test/unit/src/org/netbeans/modules/java/editor/base/semantic/DetectorTest.java
@@ -21,6 +21,7 @@ package org.netbeans.modules.java.editor.base.semantic;
import java.util.logging.Handler;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
+import javax.lang.model.SourceVersion;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import org.netbeans.api.java.source.CompilationController;
@@ -445,6 +446,35 @@ public class DetectorTest extends TestBase {
performTest("IncDecReading230408");
}
+ public void testRawStringLiteral() throws Exception {
+ try {
+ SourceVersion.valueOf("RELEASE_13");
+ } catch (IllegalArgumentException iae) {
+ //OK, presumably no support for raw string literals
+ }
+ setSourceLevel("13");
+ performTest("RawStringLiteral",
+ "public class RawStringLiteral {\n" +
+ " String s1 = \"\"\"\n" +
+ " int i1 = 1; \n" +
+ " int i2 = 2;\n" +
+ " \"\"\";\n" +
+ " String s2 = \"\"\"\n" +
+ " int i1 = 1; \n" +
+ " int i2 = 2;\n" +
+ " \"\"\";\n" +
+ "}\n",
+ "[PUBLIC, CLASS, DECLARATION], 0:13-0:29",
+ "[PUBLIC, CLASS], 1:4-1:10",
+ "[PACKAGE_PRIVATE, FIELD, DECLARATION], 1:11-1:13",
+ "[UNINDENTED_TEXT_BLOCK], 2:13-2:27",
+ "[UNINDENTED_TEXT_BLOCK], 3:13-3:29",
+ "[PUBLIC, CLASS], 5:4-5:10",
+ "[PACKAGE_PRIVATE, FIELD, DECLARATION], 5:11-5:13",
+ "[UNINDENTED_TEXT_BLOCK], 6:16-6:27",
+ "[UNINDENTED_TEXT_BLOCK], 7:16-7:29");
+ }
+
private void performTest(String fileName) throws Exception {
performTest(fileName, new Performer() {
public void compute(CompilationController parameter, Document doc, final ErrorDescriptionSetter setter) {
@@ -471,6 +501,19 @@ public class DetectorTest extends TestBase {
}, false, expected);
}
+ private void performTest(String fileName, String code, String... expected) throws Exception {
+ performTest(fileName, code, new Performer() {
+ public void compute(CompilationController parameter, Document doc, final ErrorDescriptionSetter setter) {
+ new SemanticHighlighterBase() {
+ @Override
+ protected boolean process(CompilationInfo info, Document doc) {
+ return process(info, doc, setter);
+ }
+ }.process(parameter, doc);
+ }
+ }, expected);
+ }
+
private FileObject testSourceFO;
static {
diff --git a/java/java.editor.base/test/unit/src/org/netbeans/modules/java/editor/base/semantic/HighlightImpl.java b/java/java.editor.base/test/unit/src/org/netbeans/modules/java/editor/base/semantic/HighlightImpl.java
index dffd3e5..6ee1410 100644
--- a/java/java.editor.base/test/unit/src/org/netbeans/modules/java/editor/base/semantic/HighlightImpl.java
+++ b/java/java.editor.base/test/unit/src/org/netbeans/modules/java/editor/base/semantic/HighlightImpl.java
@@ -142,6 +142,7 @@ public final class HighlightImpl {
ColoringAttributes.DECLARATION,
ColoringAttributes.MARK_OCCURRENCES,
+ ColoringAttributes.UNINDENTED_TEXT_BLOCK,
});
public static HighlightImpl parse(StyledDocument doc, String line) throws ParseException, BadLocationException {
diff --git a/java/java.editor.base/test/unit/src/org/netbeans/modules/java/editor/base/semantic/MarkOccDetTest.java b/java/java.editor.base/test/unit/src/org/netbeans/modules/java/editor/base/semantic/MarkOccDetTest.java
index cd07ba6..63f5b27 100644
--- a/java/java.editor.base/test/unit/src/org/netbeans/modules/java/editor/base/semantic/MarkOccDetTest.java
+++ b/java/java.editor.base/test/unit/src/org/netbeans/modules/java/editor/base/semantic/MarkOccDetTest.java
@@ -20,6 +20,7 @@ package org.netbeans.modules.java.editor.base.semantic;
import java.util.Collections;
import java.util.List;
+import java.util.stream.Collectors;
import javax.swing.text.Document;
import javax.swing.text.StyledDocument;
import junit.framework.Test;
@@ -27,10 +28,12 @@ import junit.framework.TestSuite;
import org.netbeans.api.java.source.CompilationController;
import org.netbeans.api.java.source.CompilationInfo;
import org.netbeans.api.java.source.test.support.MemoryValidator;
+import org.netbeans.modules.java.editor.base.semantic.ColoringAttributes.Coloring;
import org.netbeans.modules.java.editor.options.MarkOccurencesSettings;
import org.netbeans.modules.java.editor.base.semantic.TestBase.Performer;
import org.netbeans.modules.parsing.spi.SchedulerEvent;
import org.openide.text.NbDocument;
+import org.openide.util.Pair;
/**XXX: constructors throwing an exception are not marked as exit points
*
@@ -343,6 +346,9 @@ public class MarkOccDetTest extends TestBase {
performTest(name, line, column, false);
}
+ private static final Coloring MARK_OCCURRENCES =
+ ColoringAttributes.add(ColoringAttributes.empty(), ColoringAttributes.MARK_OCCURRENCES);
+
private void performTest(String name, final int line, final int column, boolean doCompileRecursively) throws Exception {
performTest(name,new Performer() {
public void compute(CompilationController info, Document doc, SemanticHighlighterBase.ErrorDescriptionSetter setter) {
@@ -350,19 +356,18 @@ public class MarkOccDetTest extends TestBase {
List<int[]> spans = new MarkOccurrencesHighlighterBase() {
@Override
protected void process(CompilationInfo info, Document doc, SchedulerEvent event) {
- throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ throw new UnsupportedOperationException("Not supported yet.");
}
}.processImpl(info, MarkOccurencesSettings.getCurrentNode(), doc, offset);
if (spans != null) {
- setter.setHighlights(doc, spans, Collections.<int[], String>emptyMap());
+ setter.setHighlights(doc, spans.stream()
+ .map(span -> Pair.of(span, MARK_OCCURRENCES))
+ .collect(Collectors.toList()),
+ Collections.<int[], String>emptyMap());
}
}
}, doCompileRecursively);
}
- protected ColoringAttributes getColoringAttribute() {
- return ColoringAttributes.MARK_OCCURRENCES;
- }
-
}
diff --git a/java/java.editor.base/test/unit/src/org/netbeans/modules/java/editor/base/semantic/TestBase.java b/java/java.editor.base/test/unit/src/org/netbeans/modules/java/editor/base/semantic/TestBase.java
index 7b2aca5..9d681b6 100644
--- a/java/java.editor.base/test/unit/src/org/netbeans/modules/java/editor/base/semantic/TestBase.java
+++ b/java/java.editor.base/test/unit/src/org/netbeans/modules/java/editor/base/semantic/TestBase.java
@@ -31,6 +31,7 @@ import java.io.StringWriter;
import java.io.Writer;
import java.net.URL;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.EnumSet;
@@ -40,6 +41,7 @@ import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.CountDownLatch;
+import java.util.stream.Collectors;
import javax.swing.text.Document;
import org.netbeans.api.java.lexer.JavaTokenId;
import org.netbeans.api.java.source.Task;
@@ -47,6 +49,7 @@ import org.netbeans.api.java.source.CompilationController;
import org.netbeans.api.java.source.JavaSource;
import org.netbeans.api.java.source.JavaSource.Phase;
import org.netbeans.api.java.source.SourceUtilsTestUtil;
+import org.netbeans.api.java.source.TestUtilities;
import org.netbeans.api.java.source.TreePathHandle;
import org.netbeans.api.lexer.Language;
import org.netbeans.api.lexer.Token;
@@ -57,6 +60,7 @@ import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.filesystems.MIMEResolver;
import org.openide.loaders.DataObject;
+import org.openide.util.Pair;
/**
*
@@ -230,8 +234,74 @@ public abstract class TestBase extends NbTestCase {
validator.validate(out.toString());
}
- protected ColoringAttributes getColoringAttribute() {
- return ColoringAttributes.UNUSED;
+ protected void performTest(String fileName, String code, Performer performer, String... expected) throws Exception {
+ performTest(fileName, code, performer, false, expected);
+ }
+
+ protected void performTest(String fileName, String code, Performer performer, boolean doCompileRecursively, String... expected) throws Exception {
+ SourceUtilsTestUtil.prepareTest(new String[] {"org/netbeans/modules/java/editor/resources/layer.xml"}, new Object[] {new MIMEResolverImpl()});
+
+ FileObject scratch = SourceUtilsTestUtil.makeScratchDir(this);
+ FileObject cache = scratch.createFolder("cache");
+
+ File wd = getWorkDir();
+ File testSource = new File(wd, "test/" + fileName + ".java");
+
+ testSource.getParentFile().mkdirs();
+ TestUtilities.copyStringToFile(testSource, code);
+
+ testSourceFO = FileUtil.toFileObject(testSource);
+
+ assertNotNull(testSourceFO);
+
+ if (sourceLevel != null) {
+ SourceUtilsTestUtil.setSourceLevel(testSourceFO, sourceLevel);
+ }
+
+ File testBuildTo = new File(wd, "test-build");
+
+ testBuildTo.mkdirs();
+
+ FileObject srcRoot = FileUtil.toFileObject(testSource.getParentFile());
+ SourceUtilsTestUtil.prepareTest(srcRoot,FileUtil.toFileObject(testBuildTo), cache);
+
+ if (doCompileRecursively) {
+ SourceUtilsTestUtil.compileRecursively(srcRoot);
+ }
+
+ final Document doc = getDocument(testSourceFO);
+ final List<HighlightImpl> highlights = new ArrayList<HighlightImpl>();
+
+ JavaSource source = JavaSource.forFileObject(testSourceFO);
+
+ assertNotNull(source);
+
+ final CountDownLatch l = new CountDownLatch(1);
+
+ source.runUserActionTask(new Task<CompilationController>() {
+ public void run(CompilationController parameter) {
+ try {
+ parameter.toPhase(Phase.UP_TO_DATE);
+
+ ErrorDescriptionSetterImpl setter = new ErrorDescriptionSetterImpl();
+
+ performer.compute(parameter, doc, setter);
+
+ highlights.addAll(setter.highlights);
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ l.countDown();
+ }
+ }
+ }, true);
+
+ l.await();
+
+ assertEquals(Arrays.asList(expected),
+ highlights.stream()
+ .map(h -> h.getHighlightTestData())
+ .collect(Collectors.toList()));
}
public static Collection<HighlightImpl> toHighlights(Document doc, Map<Token, Coloring> colors) {
@@ -290,9 +360,9 @@ public abstract class TestBase extends NbTestCase {
}
@Override
- public void setHighlights(Document doc, Collection<int[]> highlights, Map<int[], String> preText) {
- for (int[] h : highlights) {
- this.highlights.add(new HighlightImpl(doc, h[0], h[1], EnumSet.of(getColoringAttribute())));
+ public void setHighlights(Document doc, Collection<Pair<int[], Coloring>> highlights, Map<int[], String> preText) {
+ for (Pair<int[], Coloring> h : highlights) {
+ this.highlights.add(new HighlightImpl(doc, h.first()[0], h.first()[1], h.second()));
}
if (showPrependedText) {
for (Entry<int[], String> e : preText.entrySet()) {
diff --git a/java/java.editor/src/org/netbeans/modules/editor/java/TypingCompletion.java b/java/java.editor/src/org/netbeans/modules/editor/java/TypingCompletion.java
index be2f82b..1641a25 100644
--- a/java/java.editor/src/org/netbeans/modules/editor/java/TypingCompletion.java
+++ b/java/java.editor/src/org/netbeans/modules/editor/java/TypingCompletion.java
@@ -22,18 +22,24 @@ import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import java.util.prefs.Preferences;
+import javax.lang.model.SourceVersion;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import org.netbeans.api.editor.mimelookup.MimeLookup;
import org.netbeans.api.editor.settings.SimpleValueNames;
import org.netbeans.api.java.lexer.JavaTokenId;
+import static org.netbeans.api.java.source.SourceUtils.isTextBlockSupported;
import org.netbeans.api.lexer.PartType;
import org.netbeans.api.lexer.TokenHierarchy;
import org.netbeans.api.lexer.TokenSequence;
import org.netbeans.editor.BaseDocument;
+import org.netbeans.modules.editor.NbEditorUtilities;
+import org.netbeans.modules.editor.indent.api.Indent;
import org.netbeans.spi.editor.typinghooks.DeletedTextInterceptor;
import org.netbeans.spi.editor.typinghooks.TypedBreakInterceptor;
import org.netbeans.spi.editor.typinghooks.TypedTextInterceptor;
+import org.openide.filesystems.FileObject;
+import org.openide.loaders.DataObject;
/**
* This static class groups the whole aspect of bracket completion. It is
@@ -138,9 +144,16 @@ class TypingCompletion {
char chr = context.getDocument().getText(context.getOffset(), 1).charAt(0);
- if (chr == ')' || chr == ',' || chr == '\"' || chr == '\'' || chr == ' ' || chr == ']' || chr == '}' || chr == '\n' || chr == '\t' || chr == ';') {
+ if (chr == ')' || chr == ',' || chr == '\'' || chr == ' ' || chr == ']' || chr == '}' || chr == '\n' || chr == '\t' || chr == ';') {
char insChr = context.getText().charAt(0);
- context.setText("" + insChr + matching(insChr) , 1); // NOI18N
+ context.setText("" + insChr + matching(insChr), 1); // NOI18N
+ } else if (chr == '\"') {
+ if ((context.getOffset() > 2 && context.getDocument().getText(context.getOffset() - 2, 3).equals("\"\"\"")) &&
+ isTextBlockSupported(getFileObject((BaseDocument) context.getDocument()))) {
+ context.setText("\"\n\"\"\"", 2); // NOI18N
+ } else {
+ context.setText("\"\"", 1); // NOI18N
+ }
}
}
@@ -202,7 +215,6 @@ class TypingCompletion {
TokenSequence<JavaTokenId> javaTS = javaTokenSequence(context, true);
JavaTokenId id = (javaTS != null) ? javaTS.token().id() : null;
-
// If caret within comment return false
boolean caretInsideToken = (id != null)
&& (javaTS.offset() + javaTS.token().length() > context.getOffset()
@@ -214,7 +226,8 @@ class TypingCompletion {
boolean completablePosition = isQuoteCompletablePosition(context);
boolean insideString = caretInsideToken
&& (id == JavaTokenId.STRING_LITERAL
- || id == JavaTokenId.CHAR_LITERAL);
+ || id == JavaTokenId.CHAR_LITERAL
+ || id == JavaTokenId.MULTILINE_STRING_LITERAL);
int lastNonWhite = org.netbeans.editor.Utilities.getRowLastNonWhite((BaseDocument) context.getDocument(), context.getOffset());
// eol - true if the caret is at the end of line (ignoring whitespaces)
@@ -231,7 +244,7 @@ class TypingCompletion {
javaTS.move(context.getOffset() - 1);
if (javaTS.moveNext()) {
id = javaTS.token().id();
- if (id == JavaTokenId.STRING_LITERAL || id == JavaTokenId.CHAR_LITERAL) {
+ if (id == JavaTokenId.STRING_LITERAL || id == JavaTokenId.CHAR_LITERAL || id == JavaTokenId.MULTILINE_STRING_LITERAL) {
context.setText("", 0); // NOI18N
return context.getOffset() + 1;
}
@@ -242,7 +255,26 @@ class TypingCompletion {
}
if ((completablePosition && !insideString) || eol) {
- context.setText(context.getText() + context.getText(), 1);
+ if (context.getText().equals("\"") && context.getOffset() >= 2 &&
+ context.getDocument().getText(context.getOffset() - 2, 2).equals("\"\"") &&
+ isTextBlockSupported(getFileObject((BaseDocument) context.getDocument()))) {
+ context.setText("\"\n\"\"\"", 2); // NOI18N
+ Thread.dumpStack();
+ } else {
+ context.setText(context.getText() + context.getText(), 1);
+ }
+ } else if (context.getText().equals("\"") &&
+ isTextBlockSupported(getFileObject((BaseDocument) context.getDocument()))) {
+ if ((javaTS != null) && javaTS.moveNext()) {
+ id = javaTS.token().id();
+ if ((id == JavaTokenId.STRING_LITERAL) && (javaTS.token().text().toString().equals("\"\""))) {
+ if (context.getDocument().getText(context.getOffset(), 2).equals("\"\"")) {
+ context.setText("\"\"\"\n\"", 4);
+ }
+ }
+ javaTS.movePrevious();
+ id = javaTS.token().id();
+ }
}
return -1;
}
@@ -812,8 +844,13 @@ class TypingCompletion {
}
return null;
}
-
- private static Set<JavaTokenId> STRING_AND_COMMENT_TOKENS = EnumSet.of(JavaTokenId.STRING_LITERAL, JavaTokenId.LINE_COMMENT, JavaTokenId.JAVADOC_COMMENT, JavaTokenId.BLOCK_COMMENT, JavaTokenId.CHAR_LITERAL);
+
+ private static FileObject getFileObject(BaseDocument doc) {
+ DataObject dob = NbEditorUtilities.getDataObject(doc);
+ return dob.getPrimaryFile();
+ }
+
+ private static Set<JavaTokenId> STRING_AND_COMMENT_TOKENS = EnumSet.of(JavaTokenId.STRING_LITERAL, JavaTokenId.LINE_COMMENT, JavaTokenId.JAVADOC_COMMENT, JavaTokenId.BLOCK_COMMENT, JavaTokenId.CHAR_LITERAL, JavaTokenId.MULTILINE_STRING_LITERAL);
private static boolean isStringOrComment(JavaTokenId javaTokenId) {
return STRING_AND_COMMENT_TOKENS.contains(javaTokenId);
diff --git a/java/java.editor/src/org/netbeans/modules/java/editor/imports/ClipboardHandler.java b/java/java.editor/src/org/netbeans/modules/java/editor/imports/ClipboardHandler.java
index 14adc77..0620499 100644
--- a/java/java.editor/src/org/netbeans/modules/java/editor/imports/ClipboardHandler.java
+++ b/java/java.editor/src/org/netbeans/modules/java/editor/imports/ClipboardHandler.java
@@ -89,6 +89,7 @@ import org.netbeans.api.java.source.GeneratorUtilities;
import org.netbeans.api.java.source.JavaSource;
import org.netbeans.api.java.source.ModificationResult;
import org.netbeans.api.java.source.SourceUtils;
+import static org.netbeans.api.java.source.SourceUtils.isTextBlockSupported;
import org.netbeans.api.java.source.Task;
import org.netbeans.api.java.source.WorkingCopy;
import org.netbeans.api.lexer.TokenHierarchy;
@@ -98,6 +99,7 @@ import org.netbeans.editor.BaseKit;
import org.netbeans.editor.BaseKit.CutAction;
import org.netbeans.lib.editor.util.swing.DocumentUtilities;
import org.netbeans.modules.editor.NbEditorUtilities;
+import org.netbeans.modules.editor.indent.api.IndentUtils;
import org.openide.DialogDescriptor;
import org.openide.DialogDisplayer;
import org.openide.filesystems.FileObject;
@@ -621,46 +623,102 @@ public class ClipboardHandler {
private boolean delegatedImportData(final TransferSupport support) {
JComponent comp = (JComponent) support.getComponent();
- if (comp instanceof JTextComponent && !support.isDataFlavorSupported(COPY_FROM_STRING_FLAVOR) && insideToken((JTextComponent) comp, JavaTokenId.STRING_LITERAL)) {
- final Transferable t = support.getTransferable();
- return delegate.importData(comp, new Transferable() {
- @Override
- public DataFlavor[] getTransferDataFlavors() {
- return t.getTransferDataFlavors();
- }
+ if (comp instanceof JTextComponent && !support.isDataFlavorSupported(COPY_FROM_STRING_FLAVOR) ) {
+ if (insideToken((JTextComponent) comp, JavaTokenId.STRING_LITERAL)) {
+ final Transferable t = support.getTransferable();
+ return delegate.importData(comp, new Transferable() {
+ @Override
+ public DataFlavor[] getTransferDataFlavors() {
+ return t.getTransferDataFlavors();
+ }
- @Override
- public boolean isDataFlavorSupported(DataFlavor flavor) {
- return t.isDataFlavorSupported(flavor);
- }
+ @Override
+ public boolean isDataFlavorSupported(DataFlavor flavor) {
+ return t.isDataFlavorSupported(flavor);
+ }
- @Override
- public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
- Object data = t.getTransferData(flavor);
- if (data instanceof String) {
- String s = (String) data;
- s = s.replace("\\","\\\\"); //NOI18N
- s = s.replace("\"","\\\""); //NOI18N
- s = s.replace("\r\n","\n"); //NOI18N
- s = s.replace("\n","\\n\" +\n\""); //NOI18N
- data = s;
- } else if (data instanceof Reader) {
- BufferedReader br = new BufferedReader((Reader)data);
- StringBuilder sb = new StringBuilder();
- String line;
- while ((line = br.readLine()) != null) {
- line = line.replace("\\","\\\\"); //NOI18N
- line = line.replace("\"","\\\""); //NOI18N
- if (sb.length() > 0) {
- sb.append("\\n\" +\n\""); //NOI18N
+ @Override
+ public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
+ Object data = t.getTransferData(flavor);
+ if (data instanceof String) {
+ String s = (String) data;
+ s = s.replace("\\","\\\\"); //NOI18N
+ s = s.replace("\"","\\\""); //NOI18N
+ s = s.replace("\r\n","\n"); //NOI18N
+ s = s.replace("\n","\\n\" +\n\""); //NOI18N
+ data = s;
+ } else if (data instanceof Reader) {
+ BufferedReader br = new BufferedReader((Reader)data);
+ StringBuilder sb = new StringBuilder();
+ String line;
+ while ((line = br.readLine()) != null) {
+ line = line.replace("\\","\\\\"); //NOI18N
+ line = line.replace("\"","\\\""); //NOI18N
+ if (sb.length() > 0) {
+ sb.append("\\n\" +\n\""); //NOI18N
+ }
+ sb.append(line);
}
- sb.append(line);
+ data = new StringReader(sb.toString());
}
- data = new StringReader(sb.toString());
+ return data;
}
- return data;
- }
- });
+ });
+ } else if ((isTextBlockSupported(NbEditorUtilities.getFileObject(((JTextComponent)comp).getDocument()))) && insideToken((JTextComponent) comp, JavaTokenId.MULTILINE_STRING_LITERAL)) {
+ final Transferable t = support.getTransferable();
+ return delegate.importData(comp, new Transferable() {
+ @Override
+ public DataFlavor[] getTransferDataFlavors() {
+ return t.getTransferDataFlavors();
+ }
+
+ @Override
+ public boolean isDataFlavorSupported(DataFlavor flavor) {
+ return t.isDataFlavorSupported(flavor);
+ }
+
+ @Override
+ public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
+ Object data = t.getTransferData(flavor);
+ JTextComponent c = (JTextComponent) comp;
+ int indent = 0;
+ try {
+ indent = IndentUtils.lineIndent(c.getDocument(), IndentUtils.lineStartOffset(c.getDocument(), c.getCaretPosition()));
+ } catch (BadLocationException ex) {
+ Exceptions.printStackTrace(ex);
+ }
+ if (data instanceof String) {
+ String s = (String) data;
+ s = s.replace("\"\"\"","\\\"\"\""); //NOI18N
+ StringBuilder sb = new StringBuilder("");
+ for (int i = 0; i < indent; i++) {
+ sb.append(" "); //NOI18N
+ }
+ String emptySpaces = sb.toString();
+ s = s.replace("\r\n","\n"); //NOI18N
+ s = s.replace("\n",System.lineSeparator() + emptySpaces); //NOI18N
+ data = s;
+ } else if (data instanceof Reader) {
+ BufferedReader br = new BufferedReader((Reader)data);
+ StringBuilder sb = new StringBuilder();
+ String line;
+
+ while ((line = br.readLine()) != null) {
+ line = line.replace("\"\"\"", "\\\"\"\""); //NOI18N
+ if (sb.length() > 0) {
+ sb.append(System.lineSeparator()); //NOI18N
+ for (int i = 0; i < indent; i++) {
+ sb.append(" "); //NOI18N
+ }
+ }
+ sb.append(line);
+ }
+ data = new StringReader(sb.toString());
+ }
+ return data;
+ }
+ });
+ }
}
return delegate.importData(support);
}
diff --git a/java/java.editor/src/org/netbeans/modules/java/editor/resources/fontsColors.xml b/java/java.editor/src/org/netbeans/modules/java/editor/resources/fontsColors.xml
index 33a873f..668ecb7 100644
--- a/java/java.editor/src/org/netbeans/modules/java/editor/resources/fontsColors.xml
+++ b/java/java.editor/src/org/netbeans/modules/java/editor/resources/fontsColors.xml
@@ -87,6 +87,7 @@
<fontcolor name="mod-protected" />
<fontcolor name="mod-public" />
<fontcolor name="mod-keyword" default="keyword" />
+ <fontcolor name="mod-unindented-text-block" bgColor="EEDDDD" />
<!--currently not used:-->
<!-- <fontcolor name="mod-type-parameter-declaration" bgColor="lightGray"/>
diff --git a/java/java.editor/src/org/netbeans/modules/java/editor/semantic/ColoringManager.java b/java/java.editor/src/org/netbeans/modules/java/editor/semantic/ColoringManager.java
index 5ffe5de..d5ec0cf 100644
--- a/java/java.editor/src/org/netbeans/modules/java/editor/semantic/ColoringManager.java
+++ b/java/java.editor/src/org/netbeans/modules/java/editor/semantic/ColoringManager.java
@@ -85,6 +85,7 @@ public final class ColoringManager {
put("mod-unused", UNUSED);
put("mod-keyword", KEYWORD);
put("javadoc-identifier", JAVADOC_IDENTIFIER);
+ put("mod-unindented-text-block", UNINDENTED_TEXT_BLOCK);
}
private static void put(String coloring, ColoringAttributes... attributes) {
diff --git a/java/java.editor/src/org/netbeans/modules/java/editor/semantic/SemanticHighlighter.java b/java/java.editor/src/org/netbeans/modules/java/editor/semantic/SemanticHighlighter.java
index 065d8f8..c143fc9 100644
--- a/java/java.editor/src/org/netbeans/modules/java/editor/semantic/SemanticHighlighter.java
+++ b/java/java.editor/src/org/netbeans/modules/java/editor/semantic/SemanticHighlighter.java
@@ -43,6 +43,7 @@ import org.netbeans.modules.java.editor.base.semantic.SemanticHighlighterBase.Er
import org.netbeans.spi.editor.highlighting.support.OffsetsBag;
import org.netbeans.spi.editor.hints.ErrorDescription;
import org.openide.loaders.DataObject;
+import org.openide.util.Pair;
/**
*
@@ -62,13 +63,12 @@ public class SemanticHighlighter extends SemanticHighlighterBase {
public void setErrors(Document doc, List<ErrorDescription> errors, List<TreePathHandle> allUnusedImports) {}
- public void setHighlights(final Document doc, final Collection<int[]> highlights, Map<int[], String> preText) {
+ public void setHighlights(final Document doc, final Collection<Pair<int[], Coloring>> highlights, Map<int[], String> preText) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
OffsetsBag bag = new OffsetsBag(doc);
- Coloring unused = ColoringAttributes.add(ColoringAttributes.empty(), ColoringAttributes.UNUSED);
- for (int[] highlight : highlights) {
- bag.addHighlight(highlight[0], highlight[1], ColoringManager.getColoringImpl(unused));
+ for (Pair<int[], Coloring> highlight : highlights) {
+ bag.addHighlight(highlight.first()[0], highlight.first()[1], ColoringManager.getColoringImpl(highlight.second()));
}
getImportHighlightsBag(doc).setHighlights(bag);
diff --git a/java/java.editor/test/unit/src/org/netbeans/modules/editor/java/TypingCompletionUnitTest.java b/java/java.editor/test/unit/src/org/netbeans/modules/editor/java/TypingCompletionUnitTest.java
index 8345f5c..db4d1ff 100644
--- a/java/java.editor/test/unit/src/org/netbeans/modules/editor/java/TypingCompletionUnitTest.java
+++ b/java/java.editor/test/unit/src/org/netbeans/modules/editor/java/TypingCompletionUnitTest.java
@@ -23,6 +23,7 @@ import java.awt.EventQueue;
import java.awt.event.KeyEvent;
import java.util.prefs.Preferences;
import java.util.regex.Pattern;
+import javax.lang.model.SourceVersion;
import javax.swing.JEditorPane;
import javax.swing.SwingUtilities;
import javax.swing.text.BadLocationException;
@@ -1239,7 +1240,67 @@ public class TypingCompletionUnitTest extends NbTestCase {
ctx.typeChar(')');
ctx.assertDocumentTextEquals("//()|)");
}
-
+
+ public void testTextBlock1() throws Exception {
+ try {
+ SourceVersion.valueOf("RELEASE_13");
+ } catch (IllegalArgumentException ex) {
+ //OK, skip test
+ return ;
+ }
+ Context ctx = new Context(new JavaKit(), "\"\"|");
+ ctx.typeChar('\"');
+ ctx.assertDocumentTextEquals("\"\"\"\n|\"\"\"");
+ }
+
+ public void testTextBlock2() throws Exception {
+ try {
+ SourceVersion.valueOf("RELEASE_13");
+ } catch (IllegalArgumentException ex) {
+ //OK, skip test
+ return ;
+ }
+ Context ctx = new Context(new JavaKit(), "\"\"\"\n|\"\"\"");
+ ctx.typeChar('\"');
+ ctx.assertDocumentTextEquals("\"\"\"\n\"|\"\"");
+ }
+
+ public void testTextBlock3() throws Exception {
+ try {
+ SourceVersion.valueOf("RELEASE_13");
+ } catch (IllegalArgumentException ex) {
+ //OK, skip test
+ return ;
+ }
+ Context ctx = new Context(new JavaKit(), "\"\"\"\n\"|\"\"");
+ ctx.typeChar('\"');
+ ctx.assertDocumentTextEquals("\"\"\"\n\"\"|\"");
+ }
+
+ public void testTextBlock4() throws Exception {
+ try {
+ SourceVersion.valueOf("RELEASE_13");
+ } catch (IllegalArgumentException ex) {
+ //OK, skip test
+ return ;
+ }
+ Context ctx = new Context(new JavaKit(), "\"\"\"\n\"\"|\"");
+ ctx.typeChar('\"');
+ ctx.assertDocumentTextEquals("\"\"\"\n\"\"\"|");
+ }
+
+ public void testTextBlock5() throws Exception {
+ try {
+ SourceVersion.valueOf("RELEASE_13");
+ } catch (IllegalArgumentException ex) {
+ //OK, skip test:
+ return ;
+ }
+ Context ctx = new Context(new JavaKit(), "t(|\"\")");
+ ctx.typeChar('\"');
+ ctx.assertDocumentTextEquals("t(\"\"\"\n |\"\"\")");
+ }
+
public void testCorrectHandlingOfStringEscapes184059() throws Exception {
assertTrue(isInsideString("foo\n\"bar|\""));
assertTrue(isInsideString("foo\n\"bar\\\"|\""));
diff --git a/java/java.editor/test/unit/src/org/netbeans/modules/java/editor/imports/ClipboardHandlerTest.java b/java/java.editor/test/unit/src/org/netbeans/modules/java/editor/imports/ClipboardHandlerTest.java
index cab88de..3c43513 100644
--- a/java/java.editor/test/unit/src/org/netbeans/modules/java/editor/imports/ClipboardHandlerTest.java
+++ b/java/java.editor/test/unit/src/org/netbeans/modules/java/editor/imports/ClipboardHandlerTest.java
@@ -24,6 +24,7 @@ import java.io.IOException;
import java.util.concurrent.Exchanger;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
+import javax.lang.model.SourceVersion;
import javax.swing.JEditorPane;
import javax.swing.SwingUtilities;
import org.netbeans.api.java.lexer.JavaTokenId;
@@ -90,6 +91,20 @@ public class ClipboardHandlerTest extends NbTestCase {
public void testAnonymousClass() throws Exception {
copyAndPaste("package test;\nimport java.util.ArrayList;\npublic class Test { void t() { |new ArrayList<String>() {};| } }\n", "package test;\npublic class Target {\nvoid t() { ^ }\n}", "package test;\n\nimport java.util.ArrayList;\n\npublic class Target {\nvoid t() { new ArrayList<String>() {}; }\n}");
}
+
+ public void testCopyIntoTextBlock() throws Exception {
+ copyAndPaste("|List l1;\nList l2;\nList l3;\n\n| ", "package test;\npublic class Target {\nString s = \"\"\"\n^\"\"\"\n}", "package test;\npublic class Target {\nString s = \"\"\"\nList l1;\nList l2;\nList l3;\n\n\"\"\"\n}");
+ }
+
+ public void testCopyTextBlockIntoTextBlock() throws Exception {
+ try {
+ SourceVersion.valueOf("RELEASE_13");
+ } catch (IllegalArgumentException ex) {
+ //OK, skip test
+ return ;
+ }
+ copyAndPaste("|\"\"\"\nList l1;\"\"\"| ", "package test;\npublic class Target {\nString s = \"\"\"\ntest^ block\n\"\"\"\n}", "package test;\npublic class Target {\nString s = \"\"\"\ntest\\\"\"\"\nList l1;\\\"\"\" block\n\"\"\"\n}");
+ }
private void copyAndPaste(String from, final String to, String golden) throws Exception {
final int pastePos = to.indexOf('^');
diff --git a/java/java.hints/src/org/netbeans/modules/java/hints/jdk/ConvertToTextBlock.java b/java/java.hints/src/org/netbeans/modules/java/hints/jdk/ConvertToTextBlock.java
new file mode 100644
index 0000000..35f0917
--- /dev/null
+++ b/java/java.hints/src/org/netbeans/modules/java/hints/jdk/ConvertToTextBlock.java
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.java.hints.jdk;
+
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.LiteralTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.Tree.Kind;
+import com.sun.source.util.TreePath;
+import java.util.List;
+import org.netbeans.api.java.queries.CompilerOptionsQuery;
+import org.netbeans.api.java.source.CompilationInfo;
+import org.netbeans.spi.editor.hints.ErrorDescription;
+import org.netbeans.spi.editor.hints.Fix;
+import org.netbeans.spi.java.hints.ConstraintVariableType;
+import org.netbeans.spi.java.hints.ErrorDescriptionFactory;
+import org.netbeans.spi.java.hints.Hint;
+import org.netbeans.spi.java.hints.HintContext;
+import org.netbeans.spi.java.hints.JavaFix;
+import org.netbeans.spi.java.hints.TriggerPattern;
+import org.netbeans.spi.java.hints.TriggerTreeKind;
+import org.openide.util.NbBundle.Messages;
+
+@Hint(displayName = "#DN_ConvertToTextBlock", description = "#DESC_ConvertToTextBlock", category="rules15",
+ minSourceVersion = "13")
+@Messages({
+ "DN_ConvertToTextBlock=Convert to Text Block",
+ "DESC_ConvertToTextBlock=Convert to Text Block"
+})
+public class ConvertToTextBlock {
+
+ @TriggerTreeKind(Kind.PLUS)
+ @Messages("ERR_ConvertToTextBlock=Can be converted to text block")
+ public static ErrorDescription computeWarning(HintContext ctx) {
+ if (!CompilerOptionsQuery.getOptions(ctx.getInfo().getFileObject()).getArguments().contains("--enable-preview"))
+ return null;
+ if (ctx.getPath().getParentPath() != null && getTextOrNull(ctx.getPath().getParentPath()) != null) {
+ return null;
+ }
+ String text = getTextOrNull(ctx.getPath());
+ if (text == null) {
+ return null;
+ }
+ Fix fix = new FixImpl(ctx.getInfo(), ctx.getPath(), text).toEditorFix();
+ return ErrorDescriptionFactory.forName(ctx, ctx.getPath(), Bundle.ERR_ConvertToTextBlock(), fix);
+ }
+
+ private static String getTextOrNull(TreePath tp) {
+ StringBuilder text = new StringBuilder();
+ Tree current = tp.getLeaf();
+ while (current.getKind() == Kind.PLUS) {
+ BinaryTree bt = (BinaryTree) current;
+ if (bt.getRightOperand().getKind() == Kind.STRING_LITERAL) {
+ text.insert(0, ((LiteralTree) bt.getRightOperand()).getValue());
+ } else {
+ return null;
+ }
+ current = bt.getLeftOperand();
+ }
+ if (current.getKind() == Kind.STRING_LITERAL) {
+ text.insert(0, ((LiteralTree) current).getValue());
+ } else {
+ return null;
+ }
+ String textString = text.toString();
+ if (!textString.contains("\n")) {
+ return null;
+ }
+ return textString;
+ }
+
+ private static final class FixImpl extends JavaFix {
+
+ private final String text;
+
+ public FixImpl(CompilationInfo info, TreePath tp, String text) {
+ super(info, tp);
+ this.text = text;
+ }
+
+ @Override
+ @Messages("FIX_ConvertToTextBlock=Convert to text block")
+ protected String getText() {
+ return Bundle.FIX_ConvertToTextBlock();
+ }
+
+ @Override
+ protected void performRewrite(TransformationContext ctx) {
+ ctx.getWorkingCopy().rewrite(ctx.getPath().getLeaf(), ctx.getWorkingCopy().getTreeMaker().Literal(text.split("\n", -1)));
+ //perform the required transformation
+ }
+ }
+}
diff --git a/java/java.hints/src/org/netbeans/modules/java/hints/resources/jdk13.properties b/java/java.hints/src/org/netbeans/modules/java/hints/resources/jdk13.properties
new file mode 100644
index 0000000..532a56d
--- /dev/null
+++ b/java/java.hints/src/org/netbeans/modules/java/hints/resources/jdk13.properties
@@ -0,0 +1,17 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+display.name=Migrate to JDK 13
diff --git a/java/java.hints/src/org/netbeans/modules/java/hints/resources/layer.xml b/java/java.hints/src/org/netbeans/modules/java/hints/resources/layer.xml
index 689379e..8e70d6a 100644
--- a/java/java.hints/src/org/netbeans/modules/java/hints/resources/layer.xml
+++ b/java/java.hints/src/org/netbeans/modules/java/hints/resources/layer.xml
@@ -398,11 +398,27 @@
<file name="org.netbeans.modules.java.hints.jdk.ConvertToVarHint.properties" url="enabled.properties"/>
<file name="org.netbeans.modules.java.hints.jdk.ConvertSwitchToRuleSwitch.properties" url="enabled.properties"/>
</folder>
+ <folder name="rule_config_jdk13">
+ <file name="Javac_canUseDiamond.properties" url="enabled.properties"/>
+ <file name="Javac_canUseLambda.properties" url="enabled.properties"/>
+ <file name="org.netbeans.modules.java.hints.jdk.IteratorToFor.properties" url="enabled.properties"/>
+ <file name="org.netbeans.modules.java.hints.jdk.UnnecessaryUnboxing.properties" url="enabled.properties"/>
+ <file name="org.netbeans.modules.java.hints.jdk.ConvertToARM.properties" url="enabled.properties"/>
+ <file name="org.netbeans.modules.java.hints.jdk.JoinCatches.properties" url="enabled.properties"/>
+ <file name="org.netbeans.modules.java.hints.jdk.Tiny.containsForIndexOf.properties" url="enabled.properties"/>
+ <file name="org.netbeans.modules.java.hints.StaticImport.properties" url="enabled.properties"/>
+ <file name="org.netbeans.modules.java.hints.jdk.ConvertToStringSwitch.properties" url="enabled.properties"/>
+ <file name="org.netbeans.modules.java.hints.jdk.UnnecessaryBoxing.run.properties" url="enabled.properties"/>
+ <file name="org.netbeans.modules.java.hints.jdk.ConvertToVarHint.properties" url="enabled.properties"/>
+ <file name="org.netbeans.modules.java.hints.jdk.ConvertSwitchToRuleSwitch.properties" url="enabled.properties"/>
+ <file name="org.netbeans.modules.java.hints.jdk.ConvertToTextBlock.properties" url="enabled.properties"/>
+ </folder>
<folder name="ui">
<file name="rule_config_default.properties" url="nbresloc:/org/netbeans/modules/java/hints/resources/default.properties"/>
<file name="rule_config_jdk8.properties" url="nbresloc:/org/netbeans/modules/java/hints/resources/jdk8.properties"/>
<file name="rule_config_jdk11.properties" url="nbresloc:/org/netbeans/modules/java/hints/resources/jdk11.properties"/>
<file name="rule_config_jdk12.properties" url="nbresloc:/org/netbeans/modules/java/hints/resources/jdk12.properties"/>
+ <file name="rule_config_jdk13.properties" url="nbresloc:/org/netbeans/modules/java/hints/resources/jdk13.properties"/>
</folder>
</folder></folder></folder></folder></folder>
</folder>
diff --git a/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/jdk/ConvertToTextBlockTest.java b/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/jdk/ConvertToTextBlockTest.java
new file mode 100644
index 0000000..91749bd
--- /dev/null
+++ b/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/jdk/ConvertToTextBlockTest.java
@@ -0,0 +1,155 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.java.hints.jdk;
+
+import javax.lang.model.SourceVersion;
+import org.junit.Test;
+import org.netbeans.modules.java.hints.test.api.HintTest;
+
+public class ConvertToTextBlockTest {
+
+ @Test
+ public void testFixWorking() throws Exception {
+ try {
+ SourceVersion.valueOf("RELEASE_13");
+ } catch (IllegalArgumentException ex) {
+ //OK, skip test
+ return ;
+ }
+ HintTest.create()
+ .input("package test;\n" +
+ "public class Test {\n" +
+ " public static void main(String[] args) {\n" +
+ " assert args[0].equals(\"{\\n\" +\n" +
+ " \" int i = 0;\\n\" +\n" +
+ " \"}\");\n" +
+ " }\n" +
+ "}\n")
+ .sourceLevel(SourceVersion.latest().name())
+ .options("--enable-preview")
+ .run(ConvertToTextBlock.class)
+ .findWarning("3:30-3:37:verifier:" + Bundle.ERR_ConvertToTextBlock())
+ .applyFix()
+ .assertCompilable()
+ //TODO: change to match expected output
+ .assertOutput("package test;\n" +
+ "public class Test {\n" +
+ " public static void main(String[] args) {\n" +
+ " assert args[0].equals(\"\"\"\n" +
+ " {\n" +
+ " int i = 0;\n" +
+ " }\"\"\");\n" +
+ " }\n" +
+ "}\n");
+ }
+
+ @Test
+ public void testNewLineAtEnd() throws Exception {
+ try {
+ SourceVersion.valueOf("RELEASE_13");
+ } catch (IllegalArgumentException ex) {
+ //OK, skip test
+ return ;
+ }
+ HintTest.create()
+ .input("package test;\n" +
+ "public class Test {\n" +
+ " public static void main(String[] args) {\n" +
+ " assert args[0].equals(\"{\\n\" +\n" +
+ " \" int i = 0;\\n\" +\n" +
+ " \"}\\n\");\n" +
+ " }\n" +
+ "}\n")
+ .sourceLevel(SourceVersion.latest().name())
+ .options("--enable-preview")
+ .run(ConvertToTextBlock.class)
+ .findWarning("3:30-3:37:verifier:" + Bundle.ERR_ConvertToTextBlock())
+ .applyFix()
+ .assertCompilable()
+ //TODO: change to match expected output
+ .assertOutput("package test;\n" +
+ "public class Test {\n" +
+ " public static void main(String[] args) {\n" +
+ " assert args[0].equals(\"\"\"\n" +
+ " {\n" +
+ " int i = 0;\n" +
+ " }\n" +
+ " \"\"\");\n" +
+ " }\n" +
+ "}\n");
+ }
+
+ @Test
+ public void testNewLinesAtEnd() throws Exception {
+ try {
+ SourceVersion.valueOf("RELEASE_13");
+ } catch (IllegalArgumentException ex) {
+ //OK, skip test
+ return ;
+ }
+ HintTest.create()
+ .input("package test;\n" +
+ "public class Test {\n" +
+ " public static void main(String[] args) {\n" +
+ " assert args[0].equals(\"{\\n\" +\n" +
+ " \" int i = 0;\\n\" +\n" +
+ " \"}\\n\\n\");\n" +
+ " }\n" +
+ "}\n")
+ .sourceLevel(SourceVersion.latest().name())
+ .options("--enable-preview")
+ .run(ConvertToTextBlock.class)
+ .findWarning("3:30-3:37:verifier:" + Bundle.ERR_ConvertToTextBlock())
+ .applyFix()
+ .assertCompilable()
+ .assertOutput("package test;\n" +
+ "public class Test {\n" +
+ " public static void main(String[] args) {\n" +
+ " assert args[0].equals(\"\"\"\n" +
+ " {\n" +
+ " int i = 0;\n" +
+ " }\n" +
+ " \n" +
+ " \"\"\");\n" +
+ " }\n" +
+ "}\n");
+ }
+
+ @Test
+ public void testOnlyLiterals() throws Exception {
+ try {
+ SourceVersion.valueOf("RELEASE_13");
+ } catch (IllegalArgumentException ex) {
+ //OK, skip test
+ return ;
+ }
+ HintTest.create()
+ .input("package test;\n" +
+ "public class Test {\n" +
+ " public int test() {\n" +
+ " return c() + c();\n" +
+ " }\n" +
+ " private int c() { return 0; }\n" +
+ "}\n")
+ .sourceLevel(SourceVersion.latest().name())
+ .options("--enable-preview")
+ .run(ConvertToTextBlock.class)
+ .assertWarnings();
+ }
+}
diff --git a/java/java.lexer/apichanges.xml b/java/java.lexer/apichanges.xml
index 77a4f2a..c8ceebf 100644
--- a/java/java.lexer/apichanges.xml
+++ b/java/java.lexer/apichanges.xml
@@ -83,6 +83,18 @@ is the proper place.
<!-- ACTUAL CHANGES BEGIN HERE: -->
<changes>
+ <change id="RawStringLiteral">
+ <api name="general"/>
+ <summary>Added RAW_STRING_LITERAL JavaTokenKind</summary>
+ <version major="1" minor="41"/>
+ <date day="22" month="4" year="2018"/>
+ <author login="jlahoda"/>
+ <compatibility addition="yes" binary="compatible" deletion="no" deprecation="no" modification="no" semantic="compatible" source="compatible"/>
+ <description>
+ Added JavaTokenId.RAW_STRING_LITERAL.
+ </description>
+ <class package="org.netbeans.api.java.lexer" name="JavaTokenId"/>
+ </change>
<change id="Modules">
<api name="general"/>
<summary>Added tokens used in module-info.java files</summary>
diff --git a/java/java.lexer/src/org/netbeans/api/java/lexer/JavaTokenId.java b/java/java.lexer/src/org/netbeans/api/java/lexer/JavaTokenId.java
index 7cf0fbb..17602a5 100644
--- a/java/java.lexer/src/org/netbeans/api/java/lexer/JavaTokenId.java
+++ b/java/java.lexer/src/org/netbeans/api/java/lexer/JavaTokenId.java
@@ -127,6 +127,8 @@ public enum JavaTokenId implements TokenId {
DOUBLE_LITERAL(null, "number"),
CHAR_LITERAL(null, "character"),
STRING_LITERAL(null, "string"),
+ /**@since 1.41*/
+ MULTILINE_STRING_LITERAL(null, "string"),
TRUE("true", "literal"),
FALSE("false", "literal"),
diff --git a/java/java.lexer/src/org/netbeans/lib/java/lexer/JavaLexer.java b/java/java.lexer/src/org/netbeans/lib/java/lexer/JavaLexer.java
index d0a598d..8a2b651 100644
--- a/java/java.lexer/src/org/netbeans/lib/java/lexer/JavaLexer.java
+++ b/java/java.lexer/src/org/netbeans/lib/java/lexer/JavaLexer.java
@@ -170,19 +170,41 @@ public class JavaLexer implements Lexer<JavaTokenId> {
return token(JavaTokenId.ERROR);
case '"': // string literal
if (lookupId == null) lookupId = JavaTokenId.STRING_LITERAL;
- while (true)
+ while (true) {
switch (nextChar()) {
case '"': // NOI18N
+ if (this.version >= 13) {
+ String text = input.readText().toString();
+ if (text.length() == 2) {
+ if (nextChar() != '"') {
+ input.backup(1); //TODO: EOF???
+ return token(lookupId);
+ }
+ lookupId = JavaTokenId.MULTILINE_STRING_LITERAL;
+ }
+ if (lookupId == JavaTokenId.MULTILINE_STRING_LITERAL) {
+ if (text.endsWith("\"\"\"") && !text.endsWith("\\\"\"\"") && text.length() > 6) {
+ return token(lookupId);
+ } else {
+ break;
+ }
+ }
+ }
+
return token(lookupId);
case '\\':
nextChar();
break;
case '\r': consumeNewline();
case '\n':
+ if (lookupId == JavaTokenId.MULTILINE_STRING_LITERAL && this.version >= 13) {
+ break;
+ }
case EOF:
return tokenFactory.createToken(lookupId, //XXX: \n handling for exotic identifiers?
input.readLength(), PartType.START);
}
+ }
case '\'': // char literal
while (true)
diff --git a/java/java.lexer/test/unit/src/org/netbeans/lib/java/lexer/JavaLexerBatchTest.java b/java/java.lexer/test/unit/src/org/netbeans/lib/java/lexer/JavaLexerBatchTest.java
index b87cbdd..757ae28 100644
--- a/java/java.lexer/test/unit/src/org/netbeans/lib/java/lexer/JavaLexerBatchTest.java
+++ b/java/java.lexer/test/unit/src/org/netbeans/lib/java/lexer/JavaLexerBatchTest.java
@@ -106,12 +106,13 @@ public class JavaLexerBatchTest extends TestCase {
}
public void testStringLiterals() {
- String text = "\"\" \"a\"\"\" \"\\\"\" \"\\\\\" \"\\\\\\\"\" \"\\n\" \"a";
+ String text = "\"\" \"a\" \"\" \"\\\"\" \"\\\\\" \"\\\\\\\"\" \"\\n\" \"a";
TokenHierarchy<?> hi = TokenHierarchy.create(text, JavaTokenId.language());
TokenSequence<?> ts = hi.tokenSequence();
LexerTestUtilities.assertNextTokenEquals(ts, JavaTokenId.STRING_LITERAL, "\"\"");
LexerTestUtilities.assertNextTokenEquals(ts, JavaTokenId.WHITESPACE, " ");
LexerTestUtilities.assertNextTokenEquals(ts, JavaTokenId.STRING_LITERAL, "\"a\"");
+ LexerTestUtilities.assertNextTokenEquals(ts, JavaTokenId.WHITESPACE, " ");
LexerTestUtilities.assertNextTokenEquals(ts, JavaTokenId.STRING_LITERAL, "\"\"");
LexerTestUtilities.assertNextTokenEquals(ts, JavaTokenId.WHITESPACE, " ");
LexerTestUtilities.assertNextTokenEquals(ts, JavaTokenId.STRING_LITERAL, "\"\\\"\"");
@@ -697,4 +698,36 @@ public class JavaLexerBatchTest extends TestCase {
LexerTestUtilities.assertNextTokenEquals(ts, JavaTokenId.WHITESPACE, " ");
}
+ private void enableRawStringLiterals(InputAttributes attr) {
+ attr.setValue(JavaTokenId.language(), "version", (Supplier<String>) () -> {
+ return "13";
+ }, true);
+ }
+
+ public void testMultilineLiteral() {
+ String text = "\"\"\"\n2\"3\n4\\\"\"\"5\"\"\\\"6\n7\"\"\" \"\"\"wrong\"\"\" \"\"\"\nbla\n";
+ InputAttributes attr = new InputAttributes();
+ enableRawStringLiterals(attr);
+ TokenHierarchy<?> hi = TokenHierarchy.create(text, false, JavaTokenId.language(), EnumSet.noneOf(JavaTokenId.class), attr);
+ TokenSequence<?> ts = hi.tokenSequence();
+
+ LexerTestUtilities.assertNextTokenEquals(ts, JavaTokenId.MULTILINE_STRING_LITERAL, "\"\"\"\n2\"3\n4\\\"\"\"5\"\"\\\"6\n7\"\"\"");
+ LexerTestUtilities.assertNextTokenEquals(ts, JavaTokenId.WHITESPACE, " ");
+ LexerTestUtilities.assertNextTokenEquals(ts, JavaTokenId.MULTILINE_STRING_LITERAL, "\"\"\"wrong\"\"\"");
+ LexerTestUtilities.assertNextTokenEquals(ts, JavaTokenId.WHITESPACE, " ");
+ LexerTestUtilities.assertNextTokenEquals(ts, JavaTokenId.MULTILINE_STRING_LITERAL, "\"\"\"\nbla\n");
+ assertFalse(ts.moveNext());
+ }
+
+ public void testTrailing() {
+ String text = "\"\"";
+ InputAttributes attr = new InputAttributes();
+ enableRawStringLiterals(attr);
+ TokenHierarchy<?> hi = TokenHierarchy.create(text, false, JavaTokenId.language(), EnumSet.noneOf(JavaTokenId.class), attr);
+ TokenSequence<?> ts = hi.tokenSequence();
+
+ LexerTestUtilities.assertNextTokenEquals(ts, JavaTokenId.STRING_LITERAL, "\"\"");
+ assertFalse(ts.moveNext());
+ }
+
}
diff --git a/java/java.source.base/src/org/netbeans/api/java/source/SourceUtils.java b/java/java.source.base/src/org/netbeans/api/java/source/SourceUtils.java
index 8e136e2..fa7bbb4 100644
--- a/java/java.source.base/src/org/netbeans/api/java/source/SourceUtils.java
+++ b/java/java.source.base/src/org/netbeans/api/java/source/SourceUtils.java
@@ -80,13 +80,16 @@ import javax.tools.JavaFileObject;
import org.netbeans.api.annotations.common.CheckForNull;
import org.netbeans.api.annotations.common.NonNull;
+import org.netbeans.api.annotations.common.NullAllowed;
import org.netbeans.api.editor.mimelookup.MimeLookup;
import org.netbeans.api.editor.mimelookup.MimePath;
import org.netbeans.api.java.classpath.ClassPath;
import org.netbeans.api.java.classpath.JavaClassPathConstants;
import org.netbeans.api.java.lexer.JavaTokenId;
+import org.netbeans.api.java.queries.CompilerOptionsQuery;
import org.netbeans.api.java.queries.JavadocForBinaryQuery;
import org.netbeans.api.java.queries.SourceForBinaryQuery;
+import org.netbeans.api.java.queries.SourceLevelQuery;
import org.netbeans.api.java.source.ClasspathInfo.PathKind;
import org.netbeans.api.java.source.JavaSource.Phase;
import org.netbeans.api.java.source.matching.Matcher;
@@ -120,6 +123,7 @@ import org.netbeans.spi.java.classpath.support.ClassPathSupport;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.filesystems.URLMapper;
+import org.openide.modules.SpecificationVersion;
import org.openide.util.Exceptions;
import org.openide.util.Lookup;
import org.openide.util.Pair;
@@ -563,7 +567,27 @@ public class SourceUtils {
}
return null;
}
-
+
+ /**
+ * @since 13.0
+ */
+ public static boolean isTextBlockSupported(
+ @NullAllowed FileObject fileObject) {
+ SpecificationVersion supportedVer = new SpecificationVersion("13"); //NOI18N
+
+ SpecificationVersion runtimeVer = new SpecificationVersion(System.getProperty("java.specification.version")); //NOI18N
+ if (runtimeVer.compareTo(supportedVer) >= 0) {
+ if (fileObject != null) {
+ SpecificationVersion sourceVer = new SpecificationVersion(SourceLevelQuery.getSourceLevel(fileObject));
+ if (sourceVer.compareTo(supportedVer) < 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
private static FileObject findSourceForBinary(FileObject binaryRoot, FileObject binary, String signature, String pkgName, String className, boolean isPkg) throws IOException {
FileObject[] sourceRoots = SourceForBinaryQuery.findSourceRoots(binaryRoot.toURL()).getRoots();
ClassPath sourcePath = ClassPathSupport.createClassPath(sourceRoots);
diff --git a/java/java.source.base/src/org/netbeans/modules/java/source/builder/TreeFactory.java b/java/java.source.base/src/org/netbeans/modules/java/source/builder/TreeFactory.java
index 0ee4288..1e89e99 100644
--- a/java/java.source.base/src/org/netbeans/modules/java/source/builder/TreeFactory.java
+++ b/java/java.source.base/src/org/netbeans/modules/java/source/builder/TreeFactory.java
@@ -507,6 +507,8 @@ public class TreeFactory {
return make.at(NOPOS).Literal(TypeTag.INT, ((Byte) value).intValue());
if (value instanceof Short)
return make.at(NOPOS).Literal(TypeTag.INT, ((Short) value).intValue());
+ if (value instanceof String[])
+ return make.at(NOPOS).Literal(TypeTag.CLASS, value);
// workaround for making NULL_LITERAL kind.
if (value == null) {
return make.at(NOPOS).Literal(TypeTag.BOT, value);
diff --git a/java/java.source.base/src/org/netbeans/modules/java/source/pretty/CharBuffer.java b/java/java.source.base/src/org/netbeans/modules/java/source/pretty/CharBuffer.java
index 0f9a739..14eff95 100644
--- a/java/java.source.base/src/org/netbeans/modules/java/source/pretty/CharBuffer.java
+++ b/java/java.source.base/src/org/netbeans/modules/java/source/pretty/CharBuffer.java
@@ -235,17 +235,29 @@ public final class CharBuffer {
public void addTrimObserver(TrimBufferObserver o) {
trimObservers.add(o);
}
+
public void nlTerm() {
+ nlTerm(true);
+ }
+
+ public void nlTerm(boolean trim) {
if(hasMargin())
needSpace();
else {
int t = used;
if (t <= 0) return;
- while (t > 0 && chars[t-1] <= ' ') t--; // NOI18N
- trimTo(t);
+ if (trim) {
+ while (t > 0 && chars[t-1] <= ' ') t--; // NOI18N
+ trimTo(t);
+ }
append('\n'); // NOI18N
}
}
+
+ public void nlTermNoTrim() {
+ nlTerm(false);
+ }
+
public void toLineStart() {
if(hasMargin())
needSpace();
diff --git a/java/java.source.base/src/org/netbeans/modules/java/source/pretty/VeryPretty.java b/java/java.source.base/src/org/netbeans/modules/java/source/pretty/VeryPretty.java
index 5231dbc..66c0043 100644
--- a/java/java.source.base/src/org/netbeans/modules/java/source/pretty/VeryPretty.java
+++ b/java/java.source.base/src/org/netbeans/modules/java/source/pretty/VeryPretty.java
@@ -102,6 +102,7 @@ import org.netbeans.api.java.source.CodeStyle;
import org.netbeans.api.java.source.CodeStyle.*;
import org.netbeans.api.java.source.Comment;
import org.netbeans.api.java.source.Comment.Style;
+import static org.netbeans.api.java.source.SourceUtils.isTextBlockSupported;
import org.netbeans.api.lexer.TokenHierarchy;
import org.netbeans.api.lexer.TokenSequence;
import org.netbeans.modules.java.source.TreeShims;
@@ -240,6 +241,10 @@ public final class VeryPretty extends JCTree.Visitor implements DocTreeVisitor<V
out.nlTerm();
}
+ private void newLineNoTrim() {
+ out.nlTermNoTrim();
+ }
+
public void blankline() {
out.blanklines(1);
}
@@ -1873,7 +1878,34 @@ public final class VeryPretty extends JCTree.Visitor implements DocTreeVisitor<V
"\'");
break;
case CLASS:
- print("\"" + quote((String) tree.value, '\'') + "\"");
+ if (tree.value instanceof String) {
+ print("\"" + quote((String) tree.value, '\'') + "\"");
+ } else if (isTextBlockSupported(null) && tree.value instanceof String[]) {
+ int indent = out.col;
+ print("\"\"\"");
+ newline();
+ String[] lines = (String[]) tree.value;
+ for (int i = 0; i < lines.length; i++) {
+ out.toCol(indent);
+ String line = lines[i];
+ for (int c = 0; c < line.length(); c++) {
+ if (line.startsWith("\"\"\"", c)) {
+ print('\\');
+ print('"');
+ } else if (line.charAt(c) != '\'' && line.charAt(c) != '"') {
+ print(Convert.quote(line.charAt(c)));
+ } else {
+ print(line.charAt(c));
+ }
+ }
+ if (i + 1 < lines.length) {
+ newLineNoTrim();
+ }
+ }
+ print("\"\"\"");
+ } else {
+ throw new IllegalStateException("Incorrect literal value.");
+ }
break;
case BOOLEAN:
print(tree.getValue().toString());
diff --git a/java/java.source.base/src/org/netbeans/modules/java/source/save/Reindenter.java b/java/java.source.base/src/org/netbeans/modules/java/source/save/Reindenter.java
index 597c093..092df1c 100644
--- a/java/java.source.base/src/org/netbeans/modules/java/source/save/Reindenter.java
+++ b/java/java.source.base/src/org/netbeans/modules/java/source/save/Reindenter.java
@@ -68,10 +68,12 @@ import javax.tools.JavaFileObject;
import com.sun.tools.javac.parser.ParserFactory;
import com.sun.tools.javac.util.Log;
import java.util.ArrayList;
+import java.util.Arrays;
import org.netbeans.api.java.lexer.JavaTokenId;
import org.netbeans.api.java.source.CodeStyle;
import org.netbeans.api.lexer.TokenHierarchy;
import org.netbeans.api.lexer.TokenSequence;
+import org.netbeans.modules.editor.indent.api.IndentUtils;
import org.netbeans.modules.editor.indent.spi.Context;
import org.netbeans.modules.editor.indent.spi.Context.Region;
import org.netbeans.modules.editor.indent.spi.ExtraLock;
@@ -116,6 +118,7 @@ public class Reindenter implements IndentTask {
for (Region region : context.indentRegions()) {
if (initRegionData(region)) {
HashSet<Integer> linesToAddStar = new HashSet<Integer>();
+ Map<Integer, Integer> oldIndents = new HashMap<>();
LinkedList<Integer> startOffsets = getStartOffsets(region);
for (ListIterator<Integer> it = startOffsets.listIterator(); it.hasNext();) {
int startOffset = it.next();
@@ -151,6 +154,17 @@ public class Reindenter implements IndentTask {
if (!blockCommentLine.startsWith("*")) { //NOI18N
linesToAddStar.add(startOffset);
}
+ } else if (ts.token().id() == JavaTokenId.MULTILINE_STRING_LITERAL) {
+ String tokenText = ts.token().text().toString();
+ String[] lines = tokenText.split("\n");
+ int indent = Arrays.stream(lines, 1, lines.length)
+ .mapToInt(this::leadingIndent)
+ .min()
+ .orElse(0);
+ int initialLineStartOffset = context.lineStartOffset(ts.offset());
+ int indentUpdate = newIndents.getOrDefault(initialLineStartOffset, 0);
+ oldIndents.put(startOffset, indent);
+ newIndents.put(startOffset, ts.offset() - initialLineStartOffset + indentUpdate);
} else {
if (delta == 0 && ts.moveNext() && ts.token().id() == JavaTokenId.LINE_COMMENT) {
newIndents.put(startOffset, 0);
@@ -168,7 +182,12 @@ public class Reindenter implements IndentTask {
context.document().insertString(startOffset, "* ", null); //NOI18N
}
if (newIndent != null) {
- context.modifyIndent(startOffset, newIndent);
+ Integer oldIndent = oldIndents.get(startOffset);
+ if (oldIndent != null) {
+ context.modifyIndent(startOffset, oldIndent, IndentUtils.createIndentString(newIndent, true, -1));
+ } else {
+ context.modifyIndent(startOffset, newIndent);
+ }
}
if (!startOffsets.isEmpty()) {
char c;
@@ -185,6 +204,19 @@ public class Reindenter implements IndentTask {
}
}
+ private int leadingIndent(String line) {
+ int indent = 0;
+
+ for (int i = 0; i < line.length(); i++) { //TODO: code points
+ if (Character.isWhitespace(line.charAt(i)))
+ indent++;
+ else
+ break;
+ }
+
+ return indent;
+ }
+
@Override
public ExtraLock indentLock() {
return null;
diff --git a/java/java.source.base/test/unit/src/org/netbeans/api/java/source/gen/LiteralTest.java b/java/java.source.base/test/unit/src/org/netbeans/api/java/source/gen/LiteralTest.java
index 4f07d54..6feed6a 100644
--- a/java/java.source.base/test/unit/src/org/netbeans/api/java/source/gen/LiteralTest.java
+++ b/java/java.source.base/test/unit/src/org/netbeans/api/java/source/gen/LiteralTest.java
@@ -25,6 +25,7 @@ import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.VariableTree;
import java.io.File;
+import javax.lang.model.SourceVersion;
import org.netbeans.api.java.source.Task;
import org.netbeans.api.java.source.JavaSource;
import org.netbeans.api.java.source.JavaSource.Phase;
@@ -168,6 +169,93 @@ public class LiteralTest extends GeneratorTestMDRCompat {
assertEquals(golden, res);
}
+ public void testTextBlocksNew() throws Exception {
+ try {
+ SourceVersion.valueOf("RELEASE_13");
+ } catch (IllegalArgumentException ex) {
+ //OK, skip test:
+ return ;
+ }
+ testFile = new File(getWorkDir(), "Test.java");
+ TestUtilities.copyStringToFile(testFile,
+ "package hierbas.del.litoral;\n" +
+ "\n" +
+ "public class Test {\n" +
+ " public static final String C;\n" +
+ "}\n"
+ );
+ String golden =
+ "package hierbas.del.litoral;\n" +
+ "\n" +
+ "public class Test {\n" +
+ " public static final String C = \"\"\"\n" +
+ " line \"1\n" +
+ " line\\\"\\\"\\\"\"\"2\"\n" +
+ " line\\n3\"\"\";\n" +
+ "}\n";
+ JavaSource testSource = JavaSource.forFileObject(FileUtil.toFileObject(testFile));
+ Task<WorkingCopy> task = new Task<WorkingCopy>() {
+
+ public void run(WorkingCopy workingCopy) throws java.io.IOException {
+ workingCopy.toPhase(Phase.RESOLVED);
+ TreeMaker make = workingCopy.getTreeMaker();
+
+ ClassTree clazz = (ClassTree) workingCopy.getCompilationUnit().getTypeDecls().get(0);
+ VariableTree var = (VariableTree) clazz.getMembers().get(1);
+ LiteralTree val = make.Literal(new String[] {"line \"1",
+ "line\"\"\"\"\"2\"",
+ "line\n3"});
+ VariableTree nue = make.setInitialValue(var, val);
+ workingCopy.rewrite(var, nue);
+ }
+
+ };
+ testSource.runModificationTask(task).commit();
+ String res = TestUtilities.copyFileToString(testFile);
+ System.err.println(res);
+ assertEquals(golden, res);
+ }
+
+ public void testTextBlocksReplace() throws Exception {
+ testFile = new File(getWorkDir(), "Test.java");
+ TestUtilities.copyStringToFile(testFile,
+ "package hierbas.del.litoral;\n" +
+ "\n" +
+ "public class Test {\n" +
+ " public static final String C = \"\"\"\n" +
+ " old\"\"\";\n" +
+ "}\n"
+ );
+ String golden =
+ "package hierbas.del.litoral;\n" +
+ "\n" +
+ "public class Test {\n" +
+ " public static final String C = \"\"\"\n" +
+ " new\n" +
+ " \"\"\";\n" +
+ "}\n";
+ JavaSource testSource = JavaSource.forFileObject(FileUtil.toFileObject(testFile));
+ Task<WorkingCopy> task = new Task<WorkingCopy>() {
+
+ public void run(WorkingCopy workingCopy) throws java.io.IOException {
+ workingCopy.toPhase(Phase.RESOLVED);
+ TreeMaker make = workingCopy.getTreeMaker();
+
+ ClassTree clazz = (ClassTree) workingCopy.getCompilationUnit().getTypeDecls().get(0);
+ VariableTree var = (VariableTree) clazz.getMembers().get(1);
+ LiteralTree val = make.Literal(new String[] {"new",
+ ""});
+ VariableTree nue = make.setInitialValue(var, val);
+ workingCopy.rewrite(var, nue);
+ }
+
+ };
+ testSource.runModificationTask(task).commit();
+ String res = TestUtilities.copyFileToString(testFile);
+ System.err.println(res);
+ assertEquals(golden, res);
+ }
+
String getGoldenPckg() {
return "";
}
diff --git a/java/java.source.base/test/unit/src/org/netbeans/modules/java/source/save/ReindenterTest.java b/java/java.source.base/test/unit/src/org/netbeans/modules/java/source/save/ReindenterTest.java
index 4c49ae7..3848fb4 100644
--- a/java/java.source.base/test/unit/src/org/netbeans/modules/java/source/save/ReindenterTest.java
+++ b/java/java.source.base/test/unit/src/org/netbeans/modules/java/source/save/ReindenterTest.java
@@ -20,6 +20,7 @@ package org.netbeans.modules.java.source.save;
import java.util.prefs.Preferences;
import java.util.regex.Pattern;
+import javax.lang.model.SourceVersion;
import javax.swing.text.Document;
import javax.swing.text.PlainDocument;
import org.netbeans.api.editor.mimelookup.MimeLookup;
@@ -1943,7 +1944,39 @@ public class ReindenterTest extends NbTestCase {
"package t;\npublic class T {\n public void op() {\n int a = switch(get())\n {\n }\n }\n}\n");
}
-
+ public void testNewLineIndentationTextBlock1() throws Exception {
+ try {
+ SourceVersion.valueOf("RELEASE_13");
+ } catch (IllegalArgumentException ex) {
+ //OK, skip test:
+ return ;
+ }
+ performNewLineIndentationTest("package t;\npublic class T {\n private final String s = \"\"\"|\n}\n",
+ "package t;\npublic class T {\n private final String s = \"\"\"\n \n}\n");
+ }
+
+ public void testSpanIndentationTextBlock1() throws Exception {
+ try {
+ SourceVersion.valueOf("RELEASE_13");
+ } catch (IllegalArgumentException ex) {
+ //OK, skip test:
+ return ;
+ }
+ performSpanIndentationTest("package t;\npublic class T {\n|private final String s = \"\"\"\n\"\"\";|\n}\n",
+ "package t;\npublic class T {\n private final String s = \"\"\"\n \"\"\";\n}\n");
+ }
+
+ public void testSpanIndentationTextBlock2() throws Exception {
+ try {
+ SourceVersion.valueOf("RELEASE_13");
+ } catch (IllegalArgumentException ex) {
+ //OK, skip test:
+ return ;
+ }
+ performSpanIndentationTest("package t;\npublic class T {\n|private final String s = \"\"\"\n1\n 2\n 3\n\"\"\";|\n}\n",
+ "package t;\npublic class T {\n private final String s = \"\"\"\n 1\n 2\n 3\n \"\"\";\n}\n");
+ }
+
private void performNewLineIndentationTest(String code, String golden) throws Exception {
int pos = code.indexOf('|');
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@netbeans.apache.org
For additional commands, e-mail: commits-help@netbeans.apache.org
For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists