Skip to content

Commit 3b395b3

Browse files
authored
Merge pull request #32 from SeeSharpSoft/fb_annotator
[FEATURE] CSV Annotator added
2 parents 6aa5404 + 2913c17 commit 3b395b3

File tree

14 files changed

+170
-91
lines changed

14 files changed

+170
-91
lines changed
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package net.seesharpsoft.intellij.plugins.csv;
2+
3+
import com.intellij.lang.annotation.*;
4+
import com.intellij.openapi.editor.markup.AttributesFlyweight;
5+
import com.intellij.openapi.editor.markup.TextAttributes;
6+
import com.intellij.psi.PsiElement;
7+
import com.intellij.xml.util.XmlStringUtil;
8+
import net.seesharpsoft.intellij.plugins.csv.psi.CsvFile;
9+
import net.seesharpsoft.intellij.plugins.csv.psi.CsvTypes;
10+
import org.jetbrains.annotations.NotNull;
11+
12+
import java.util.Map;
13+
14+
public class CsvAnnotator implements Annotator {
15+
16+
private static final TextAttributes EMPTY_TEXT_ATTRIBUTES = TextAttributes.fromFlyweight(AttributesFlyweight.create(null, null, 0, null, null, null));
17+
18+
@Override
19+
public void annotate(@NotNull final PsiElement element, @NotNull AnnotationHolder holder) {
20+
if (CsvHelper.getElementType(element) != CsvTypes.FIELD) {
21+
return;
22+
}
23+
24+
AnnotationSession currentAnnotationSession = holder.getCurrentAnnotationSession();
25+
Map<Integer, CsvColumnInfo<PsiElement>> columnInfoMap = currentAnnotationSession.getUserData(CsvHelper.COLUMN_INFO_KEY);
26+
if (columnInfoMap == null) {
27+
columnInfoMap = CsvHelper.createColumnInfoMap((CsvFile)element.getContainingFile());
28+
currentAnnotationSession.putUserData(CsvHelper.COLUMN_INFO_KEY, columnInfoMap);
29+
}
30+
31+
CsvColumnInfo<PsiElement> columnInfo = getColumnInfo(element, columnInfoMap);
32+
33+
if (columnInfo != null) {
34+
PsiElement headerElement = columnInfo.getHeaderElement();
35+
String message = XmlStringUtil.escapeString(headerElement == null ? "" : headerElement.getText(), true);
36+
String tooltip = XmlStringUtil.wrapInHtml(String.format("%s<br /><br />Column: %s<br />Index: %d", XmlStringUtil.escapeString(element.getText(), true), message, columnInfo.getColumnIndex()));
37+
38+
Annotation annotation = holder.createAnnotation(HighlightSeverity.WARNING, element.getTextRange(), message, tooltip);
39+
annotation.setEnforcedTextAttributes(EMPTY_TEXT_ATTRIBUTES);
40+
annotation.setNeedsUpdateOnTyping(false);
41+
}
42+
}
43+
44+
protected CsvColumnInfo<PsiElement> getColumnInfo(@NotNull final PsiElement element, @NotNull Map<Integer, CsvColumnInfo<PsiElement>> columnInfoMap) {
45+
for (Map.Entry<Integer, CsvColumnInfo<PsiElement>> entry : columnInfoMap.entrySet()) {
46+
CsvColumnInfo<PsiElement> columnInfo = entry.getValue();
47+
if (columnInfo.containsElement(element)) {
48+
return columnInfo;
49+
}
50+
}
51+
return null;
52+
}
53+
}

src/main/java/net/seesharpsoft/intellij/plugins/csv/CsvHelper.java

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,68 @@
11
package net.seesharpsoft.intellij.plugins.csv;
22

3+
import com.intellij.lang.*;
4+
import com.intellij.lexer.Lexer;
5+
import com.intellij.openapi.project.Project;
6+
import com.intellij.openapi.util.Key;
37
import com.intellij.psi.PsiElement;
8+
import com.intellij.psi.PsiManager;
9+
import com.intellij.psi.TokenType;
10+
import com.intellij.psi.impl.source.DummyHolder;
11+
import com.intellij.psi.impl.source.DummyHolderFactory;
12+
import com.intellij.psi.impl.source.tree.FileElement;
13+
import com.intellij.psi.tree.IElementType;
414
import com.intellij.psi.util.PsiTreeUtil;
515
import net.seesharpsoft.intellij.plugins.csv.psi.CsvField;
616
import net.seesharpsoft.intellij.plugins.csv.psi.CsvFile;
717
import net.seesharpsoft.intellij.plugins.csv.psi.CsvRecord;
18+
import net.seesharpsoft.intellij.plugins.csv.psi.CsvTypes;
819

920
import java.util.HashMap;
1021
import java.util.Map;
1122

1223
public class CsvHelper {
1324

25+
public static final Key<Map<Integer, CsvColumnInfo<PsiElement>>> COLUMN_INFO_KEY = Key.create("COLUMN_INFO_KEY");
26+
27+
// replaces PsiElementFactory.SERVICE.getInstance(element.getProject()).createDummyHolder("<undefined>", CsvTypes.FIELD, null);
28+
// https://github.com/SeeSharpSoft/intellij-csv-validator/issues/4
29+
public static PsiElement createEmptyCsvField(Project project) {
30+
final String text = "<undefined>";
31+
final IElementType type = CsvTypes.FIELD;
32+
final PsiManager psiManager = PsiManager.getInstance(project);
33+
final DummyHolder dummyHolder = DummyHolderFactory.createHolder(psiManager, null);
34+
final FileElement fileElement = dummyHolder.getTreeElement();
35+
final ParserDefinition parserDefinition = LanguageParserDefinitions.INSTANCE.forLanguage(CsvLanguage.INSTANCE);
36+
final Lexer lexer = parserDefinition.createLexer(project);
37+
final PsiBuilder psiBuilder = PsiBuilderFactory.getInstance().createBuilder(project, fileElement, lexer, CsvLanguage.INSTANCE, text);
38+
final ASTNode node = parserDefinition.createParser(project).parse(type, psiBuilder);
39+
fileElement.rawAddChildren((com.intellij.psi.impl.source.tree.TreeElement)node);
40+
return node.getPsi();
41+
}
42+
43+
public static IElementType getElementType(PsiElement element) {
44+
return element == null || element.getNode() == null ? null : element.getNode().getElementType();
45+
}
46+
47+
public static PsiElement getParentFieldElement(PsiElement element) {
48+
if (CsvHelper.getElementType(element) == TokenType.WHITE_SPACE) {
49+
if (CsvHelper.getElementType(element.getParent()) == CsvTypes.FIELD) {
50+
element = element.getParent();
51+
} else if (CsvHelper.getElementType(element.getPrevSibling()) == CsvTypes.FIELD) {
52+
element = element.getPrevSibling();
53+
} else if (CsvHelper.getElementType(element.getNextSibling()) == CsvTypes.FIELD) {
54+
element = element.getNextSibling();
55+
} else {
56+
element = null;
57+
}
58+
} else {
59+
while (element != null && CsvHelper.getElementType(element) != CsvTypes.FIELD) {
60+
element = element.getParent();
61+
}
62+
}
63+
return element;
64+
}
65+
1466
public static Map<Integer, CsvColumnInfo<PsiElement>> createColumnInfoMap(CsvFile csvFile) {
1567
Map<Integer, CsvColumnInfo<PsiElement>> columnInfoMap = new HashMap<>();
1668
CsvRecord[] records = PsiTreeUtil.getChildrenOfType(csvFile, CsvRecord.class);

src/main/java/net/seesharpsoft/intellij/plugins/csv/intention/CsvIntentionHelper.java

Lines changed: 21 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,8 @@
66
import com.intellij.psi.PsiDocumentManager;
77
import com.intellij.psi.PsiElement;
88
import com.intellij.psi.PsiFile;
9-
import com.intellij.psi.TokenType;
10-
import com.intellij.psi.tree.IElementType;
119
import com.intellij.util.IncorrectOperationException;
10+
import net.seesharpsoft.intellij.plugins.csv.CsvHelper;
1211
import net.seesharpsoft.intellij.plugins.csv.psi.CsvTypes;
1312
import org.jetbrains.annotations.NotNull;
1413

@@ -32,33 +31,10 @@ public static List<PsiElement> getChildren(PsiElement element) {
3231
}
3332
return children;
3433
}
35-
36-
public static IElementType getElementType(PsiElement element) {
37-
return element == null || element.getNode() == null ? null : element.getNode().getElementType();
38-
}
3934

40-
public static PsiElement getParentFieldElement(PsiElement element) {
41-
if (getElementType(element) == TokenType.WHITE_SPACE) {
42-
if (getElementType(element.getParent()) == CsvTypes.FIELD) {
43-
element = element.getParent();
44-
} else if (getElementType(element.getPrevSibling()) == CsvTypes.FIELD) {
45-
element = element.getPrevSibling();
46-
} else if (getElementType(element.getNextSibling()) == CsvTypes.FIELD) {
47-
element = element.getNextSibling();
48-
} else {
49-
element = null;
50-
}
51-
} else {
52-
while (element != null && CsvIntentionHelper.getElementType(element) != CsvTypes.FIELD) {
53-
element = element.getParent();
54-
}
55-
}
56-
return element;
57-
}
58-
5935
public static PsiElement getPreviousSeparator(PsiElement fieldElement) {
6036
while (fieldElement != null) {
61-
if (getElementType(fieldElement) == CsvTypes.COMMA) {
37+
if (CsvHelper.getElementType(fieldElement) == CsvTypes.COMMA) {
6238
break;
6339
}
6440
fieldElement = fieldElement.getPrevSibling();
@@ -68,7 +44,7 @@ public static PsiElement getPreviousSeparator(PsiElement fieldElement) {
6844

6945
public static PsiElement getNextSeparator(PsiElement fieldElement) {
7046
while (fieldElement != null) {
71-
if (getElementType(fieldElement) == CsvTypes.COMMA) {
47+
if (CsvHelper.getElementType(fieldElement) == CsvTypes.COMMA) {
7248
break;
7349
}
7450
fieldElement = fieldElement.getNextSibling();
@@ -78,7 +54,7 @@ public static PsiElement getNextSeparator(PsiElement fieldElement) {
7854

7955
public static PsiElement getPreviousCRLF(PsiElement recordElement) {
8056
while (recordElement != null) {
81-
if (getElementType(recordElement) == CsvTypes.CRLF) {
57+
if (CsvHelper.getElementType(recordElement) == CsvTypes.CRLF) {
8258
break;
8359
}
8460
recordElement = recordElement.getPrevSibling();
@@ -88,7 +64,7 @@ public static PsiElement getPreviousCRLF(PsiElement recordElement) {
8864

8965
public static PsiElement getNextCRLF(PsiElement recordElement) {
9066
while (recordElement != null) {
91-
if (getElementType(recordElement) == CsvTypes.CRLF) {
67+
if (CsvHelper.getElementType(recordElement) == CsvTypes.CRLF) {
9268
break;
9369
}
9470
recordElement = recordElement.getNextSibling();
@@ -110,9 +86,9 @@ public static Collection<PsiElement> getAllElements(PsiFile file) {
11086

11187
private static Collection<PsiElement> getAllFields(PsiFile file) {
11288
return getChildren(file).parallelStream()
113-
.filter(element -> getElementType(element) == CsvTypes.RECORD)
89+
.filter(element -> CsvHelper.getElementType(element) == CsvTypes.RECORD)
11490
.flatMap(record -> getChildren(record).stream())
115-
.filter(element -> getElementType(element) == CsvTypes.FIELD)
91+
.filter(element -> CsvHelper.getElementType(element) == CsvTypes.FIELD)
11692
.collect(Collectors.toList());
11793
}
11894

@@ -123,15 +99,15 @@ public static void quoteAll(@NotNull Project project, @NotNull PsiFile psiFile)
12399
Collection<PsiElement> fields = getAllFields(psiFile);
124100
PsiElement separator;
125101
for (PsiElement field : fields) {
126-
if (field.getFirstChild() == null || getElementType(field.getFirstChild()) != CsvTypes.QUOTE) {
102+
if (field.getFirstChild() == null || CsvHelper.getElementType(field.getFirstChild()) != CsvTypes.QUOTE) {
127103
separator = getPreviousSeparator(field);
128104
if (separator == null) {
129105
quotePositions.add(field.getParent().getTextOffset());
130106
} else {
131107
quotePositions.add(separator.getTextOffset() + separator.getTextLength());
132108
}
133109
}
134-
if (field.getLastChild() == null || getElementType(field.getLastChild()) != CsvTypes.QUOTE) {
110+
if (field.getLastChild() == null || CsvHelper.getElementType(field.getLastChild()) != CsvTypes.QUOTE) {
135111
separator = getNextSeparator(field);
136112
if (separator == null) {
137113
quotePositions.add(field.getParent().getTextOffset() + field.getParent().getTextLength());
@@ -153,13 +129,13 @@ public static void unquoteAll(@NotNull Project project, @NotNull PsiFile psiFile
153129
List<Integer> quotePositions = new ArrayList<>();
154130
Collection<PsiElement> fields = getAllFields(psiFile);
155131
for (PsiElement field : fields) {
156-
if (getChildren(field).stream().anyMatch(element -> getElementType(element) == CsvTypes.ESCAPED_TEXT)) {
132+
if (getChildren(field).stream().anyMatch(element -> CsvHelper.getElementType(element) == CsvTypes.ESCAPED_TEXT)) {
157133
continue;
158134
}
159-
if (getElementType(field.getFirstChild()) == CsvTypes.QUOTE) {
135+
if (CsvHelper.getElementType(field.getFirstChild()) == CsvTypes.QUOTE) {
160136
quotePositions.add(field.getFirstChild().getTextOffset());
161137
}
162-
if (getElementType(field.getLastChild()) == CsvTypes.QUOTE) {
138+
if (CsvHelper.getElementType(field.getLastChild()) == CsvTypes.QUOTE) {
163139
quotePositions.add(field.getLastChild().getTextOffset());
164140
}
165141
}
@@ -175,7 +151,7 @@ public static void quoteValue(@NotNull Project project, @NotNull PsiElement elem
175151
Document document = PsiDocumentManager.getInstance(project).getDocument(element.getContainingFile());
176152
List<Integer> quotePositions = new ArrayList<>();
177153

178-
element = getParentFieldElement(element);
154+
element = CsvHelper.getParentFieldElement(element);
179155
int quotePosition = getOpeningQuotePosition(element.getFirstChild(), element.getLastChild());
180156
if (quotePosition != -1) {
181157
quotePositions.add(quotePosition);
@@ -198,11 +174,11 @@ public static void unquoteValue(@NotNull Project project, @NotNull PsiElement el
198174
Document document = PsiDocumentManager.getInstance(project).getDocument(element.getContainingFile());
199175
List<Integer> quotePositions = new ArrayList<>();
200176

201-
element = getParentFieldElement(element);
202-
if (getElementType(element.getFirstChild()) == CsvTypes.QUOTE) {
177+
element = CsvHelper.getParentFieldElement(element);
178+
if (CsvHelper.getElementType(element.getFirstChild()) == CsvTypes.QUOTE) {
203179
quotePositions.add(element.getFirstChild().getTextOffset());
204180
}
205-
if (getElementType(element.getLastChild()) == CsvTypes.QUOTE) {
181+
if (CsvHelper.getElementType(element.getLastChild()) == CsvTypes.QUOTE) {
206182
quotePositions.add(element.getLastChild().getTextOffset());
207183
}
208184
String text = removeQuotes(document.getText(), quotePositions);
@@ -235,22 +211,22 @@ public static String removeQuotes(String text, List<Integer> quotePositions) {
235211
}
236212

237213
public static int getOpeningQuotePosition(PsiElement firstFieldElement, PsiElement lastFieldElement) {
238-
if (getElementType(firstFieldElement) != CsvTypes.QUOTE) {
214+
if (CsvHelper.getElementType(firstFieldElement) != CsvTypes.QUOTE) {
239215
return firstFieldElement.getTextOffset();
240216
}
241-
if (getElementType(lastFieldElement) == CsvTypes.QUOTE) {
217+
if (CsvHelper.getElementType(lastFieldElement) == CsvTypes.QUOTE) {
242218
return lastFieldElement.getTextOffset();
243219
}
244220
return -1;
245221
}
246222

247223
public static int getOpeningQuotePosition(PsiElement errorElement) {
248224
PsiElement lastFieldElement = errorElement;
249-
while(getElementType(lastFieldElement) != CsvTypes.RECORD) {
225+
while(CsvHelper.getElementType(lastFieldElement) != CsvTypes.RECORD) {
250226
lastFieldElement = lastFieldElement.getPrevSibling();
251227
}
252228
lastFieldElement = lastFieldElement.getLastChild();
253-
if (getElementType(lastFieldElement) != CsvTypes.FIELD) {
229+
if (CsvHelper.getElementType(lastFieldElement) != CsvTypes.FIELD) {
254230
throw new RuntimeException("Field element expected");
255231
}
256232
return getOpeningQuotePosition(lastFieldElement.getFirstChild(), lastFieldElement.getLastChild());
@@ -260,7 +236,7 @@ public static int getOpeningQuotePosition(PsiElement errorElement) {
260236
public static PsiElement findQuotePositionsUntilSeparator(PsiElement element, List<Integer> quotePositions) {
261237
PsiElement separatorElement = null;
262238
while (separatorElement == null && element != null) {
263-
if (getElementType(element) == CsvTypes.COMMA || getElementType(element) == CsvTypes.CRLF) {
239+
if (CsvHelper.getElementType(element) == CsvTypes.COMMA || CsvHelper.getElementType(element) == CsvTypes.CRLF) {
264240
separatorElement = element;
265241
continue;
266242
}

src/main/java/net/seesharpsoft/intellij/plugins/csv/intention/CsvQuoteAllIntentionAction.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import com.intellij.psi.PsiElement;
66
import com.intellij.psi.TokenType;
77
import com.intellij.util.IncorrectOperationException;
8+
import net.seesharpsoft.intellij.plugins.csv.CsvHelper;
89
import org.jetbrains.annotations.NonNls;
910
import org.jetbrains.annotations.NotNull;
1011
import org.jetbrains.annotations.Nullable;
@@ -23,7 +24,7 @@ public boolean isAvailable(@NotNull Project project, Editor editor, @Nullable Ps
2324
}
2425

2526
return !CsvIntentionHelper.getAllElements(element.getContainingFile()).stream()
26-
.anyMatch(psiElement -> CsvIntentionHelper.getElementType(psiElement) == TokenType.BAD_CHARACTER);
27+
.anyMatch(psiElement -> CsvHelper.getElementType(psiElement) == TokenType.BAD_CHARACTER);
2728
}
2829

2930
@Override

src/main/java/net/seesharpsoft/intellij/plugins/csv/intention/CsvQuoteValueIntentionAction.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import com.intellij.openapi.project.Project;
55
import com.intellij.psi.PsiElement;
66
import com.intellij.util.IncorrectOperationException;
7+
import net.seesharpsoft.intellij.plugins.csv.CsvHelper;
78
import net.seesharpsoft.intellij.plugins.csv.psi.CsvField;
89
import net.seesharpsoft.intellij.plugins.csv.psi.CsvTypes;
910
import org.jetbrains.annotations.NonNls;
@@ -23,16 +24,16 @@ public boolean isAvailable(@NotNull Project project, Editor editor, @Nullable Ps
2324
return false;
2425
}
2526

26-
element = CsvIntentionHelper.getParentFieldElement(element);
27+
element = CsvHelper.getParentFieldElement(element);
2728
return element instanceof CsvField &&
2829
element.getFirstChild() != null &&
29-
(CsvIntentionHelper.getElementType(element.getFirstChild()) != CsvTypes.QUOTE ||
30-
CsvIntentionHelper.getElementType(element.getLastChild()) != CsvTypes.QUOTE);
30+
(CsvHelper.getElementType(element.getFirstChild()) != CsvTypes.QUOTE ||
31+
CsvHelper.getElementType(element.getLastChild()) != CsvTypes.QUOTE);
3132
}
3233

3334
@Override
3435
public void invoke(@NotNull Project project, Editor editor, PsiElement element) throws IncorrectOperationException {
35-
CsvIntentionHelper.quoteValue(project, CsvIntentionHelper.getParentFieldElement(element));
36+
CsvIntentionHelper.quoteValue(project, CsvHelper.getParentFieldElement(element));
3637
}
3738

3839
}

src/main/java/net/seesharpsoft/intellij/plugins/csv/intention/CsvShiftColumnLeftIntentionAction.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public CsvShiftColumnLeftIntentionAction() {
2121
public void invoke(@NotNull Project project, Editor editor, @NotNull PsiElement psiElement) throws IncorrectOperationException {
2222
CsvFile psiFile = (CsvFile)psiElement.getContainingFile();
2323

24-
psiElement = CsvIntentionHelper.getParentFieldElement(psiElement);
24+
psiElement = CsvHelper.getParentFieldElement(psiElement);
2525

2626
Map<Integer, CsvColumnInfo<PsiElement>> columnInfoMap = CsvHelper.createColumnInfoMap(psiFile);
2727

src/main/java/net/seesharpsoft/intellij/plugins/csv/intention/CsvShiftColumnRightIntentionAction.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public CsvShiftColumnRightIntentionAction() {
2121
public void invoke(@NotNull Project project, Editor editor, @NotNull PsiElement psiElement) throws IncorrectOperationException {
2222
CsvFile psiFile = (CsvFile)psiElement.getContainingFile();
2323

24-
psiElement = CsvIntentionHelper.getParentFieldElement(psiElement);
24+
psiElement = CsvHelper.getParentFieldElement(psiElement);
2525

2626
Map<Integer, CsvColumnInfo<PsiElement>> columnInfoMap = CsvHelper.createColumnInfoMap(psiFile);
2727

src/main/java/net/seesharpsoft/intellij/plugins/csv/intention/CsvUnquoteAllIntentionAction.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import com.intellij.psi.PsiElement;
66
import com.intellij.psi.TokenType;
77
import com.intellij.util.IncorrectOperationException;
8+
import net.seesharpsoft.intellij.plugins.csv.CsvHelper;
89
import org.jetbrains.annotations.NonNls;
910
import org.jetbrains.annotations.NotNull;
1011
import org.jetbrains.annotations.Nullable;
@@ -23,7 +24,7 @@ public boolean isAvailable(@NotNull Project project, Editor editor, @Nullable Ps
2324
}
2425

2526
return !CsvIntentionHelper.getAllElements(element.getContainingFile()).stream()
26-
.anyMatch(psiElement -> CsvIntentionHelper.getElementType(psiElement) == TokenType.BAD_CHARACTER);
27+
.anyMatch(psiElement -> CsvHelper.getElementType(psiElement) == TokenType.BAD_CHARACTER);
2728
}
2829

2930
@Override

0 commit comments

Comments
 (0)