Skip to content

Commit b847b35

Browse files
committed
[FEATURE] support for comments
1 parent e44e622 commit b847b35

File tree

15 files changed

+215
-22
lines changed

15 files changed

+215
-22
lines changed

src/main/java/net/seesharpsoft/intellij/plugins/csv/Csv.bnf

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,13 @@
1919
COMMA='regexp:[,:;|\t]'
2020
QUOTE='regexp:"'
2121
CRLF='regexp:\n'
22+
COMMENT='regexp:#.*(\n|$)'
2223
]
2324
}
2425

2526
csvFile ::= record (CRLF record)* [CRLF]
2627

27-
record ::= field (COMMA field)*
28+
record ::= (COMMENT | (field (COMMA field)*))
2829

2930
field ::= (escaped | nonEscaped)
3031

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

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,18 @@ public class CsvColumnInfoMap<T> {
1010
private final Map<T, CsvColumnInfo<T>> myReverseInfoColumnMap;
1111

1212
private boolean hasErrors = false;
13+
private boolean hasComments = false;
1314

14-
public CsvColumnInfoMap(Map<Integer, CsvColumnInfo<T>> infoColumnMap, boolean hasErrorsArg) {
15+
public CsvColumnInfoMap(Map<Integer, CsvColumnInfo<T>> infoColumnMap, boolean hasErrorsArg, boolean hasCommentsArg) {
1516
this.myInfoColumnMap = infoColumnMap;
1617
this.myReverseInfoColumnMap = new HashMap<>();
1718
buildReverseMap();
1819
setHasErrors(hasErrorsArg);
20+
setHasComments(hasCommentsArg);
1921
}
2022

2123
public CsvColumnInfoMap(Map<Integer, CsvColumnInfo<T>> infoColumnMap) {
22-
this(infoColumnMap, false);
24+
this(infoColumnMap, false, false);
2325
}
2426

2527
private void buildReverseMap() {
@@ -55,6 +57,14 @@ public void setHasErrors(boolean hasErrorsArg) {
5557
hasErrors = hasErrorsArg;
5658
}
5759

60+
public boolean hasComments() {
61+
return hasComments;
62+
}
63+
64+
public void setHasComments(boolean hasCommentsArg) {
65+
hasComments = hasCommentsArg;
66+
}
67+
5868
public boolean hasEmptyLastLine() {
5969
CsvColumnInfo<T> columnInfo = myInfoColumnMap.get(0);
6070
int size = columnInfo.getSize();

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ public static CsvColumnInfoMap<PsiElement> createColumnInfoMap(CsvFile csvFile)
201201
Map<Integer, CsvColumnInfo<PsiElement>> columnInfoMap = new HashMap<>();
202202
CsvRecord[] records = PsiTreeUtil.getChildrenOfType(csvFile, CsvRecord.class);
203203
int row = 0;
204+
boolean hasComments = false;
204205
for (CsvRecord record : records) {
205206
int column = 0;
206207
for (CsvField field : record.getFieldList()) {
@@ -213,9 +214,12 @@ public static CsvColumnInfoMap<PsiElement> createColumnInfoMap(CsvFile csvFile)
213214
columnInfoMap.get(column).addElement(field, row, getFieldStartOffset(field), getFieldEndOffset(field));
214215
++column;
215216
}
217+
if (record.getComment() != null) {
218+
hasComments = true;
219+
}
216220
++row;
217221
}
218-
return new CsvColumnInfoMap(columnInfoMap, PsiTreeUtil.hasErrorElements(csvFile));
222+
return new CsvColumnInfoMap(columnInfoMap, PsiTreeUtil.hasErrorElements(csvFile), hasComments);
219223
}
220224

221225
public static String unquoteCsvValue(String content, CsvEscapeCharacter escapeCharacter) {

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import com.intellij.openapi.project.Project;
55
import com.intellij.openapi.vfs.VirtualFile;
66
import com.intellij.psi.PsiFile;
7+
import net.seesharpsoft.intellij.plugins.csv.settings.CsvEditorSettings;
78
import org.jetbrains.annotations.NotNull;
89

910
public class CsvLexerFactory {
@@ -14,12 +15,13 @@ public static CsvLexerFactory getInstance() {
1415
}
1516

1617
protected Lexer createLexer(@NotNull CsvValueSeparator separator, @NotNull CsvEscapeCharacter escapeCharacter) {
17-
if (separator.isCustom()) {
18+
if (separator.isCustom() || !CsvEditorSettings.getInstance().getCommentIndicator().isEmpty()) {
1819
return new CsvSharpLexer(new CsvSharpLexer.Configuration(
1920
separator.getCharacter(),
2021
"\n",
2122
escapeCharacter.getCharacter(),
22-
"\""));
23+
"\"",
24+
CsvEditorSettings.getInstance().getCommentIndicator()));
2325
}
2426
return new CsvLexerAdapter(separator, escapeCharacter);
2527
}

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

Lines changed: 58 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import net.seesharpsoft.UnhandledSwitchCaseException;
66
import net.seesharpsoft.commons.util.Tokenizer;
77
import net.seesharpsoft.intellij.plugins.csv.psi.CsvTypes;
8+
import net.seesharpsoft.intellij.plugins.csv.settings.CsvEditorSettings;
89
import org.jetbrains.annotations.NotNull;
910
import org.jetbrains.annotations.Nullable;
1011

@@ -18,6 +19,7 @@
1819
public class CsvSharpLexer extends LexerBase {
1920

2021
private final Tokenizer<TokenType> tokenizer;
22+
private final List<Tokenizer.Token<TokenType>> initialNextStateTokens;
2123
private final List<Tokenizer.Token<TokenType>> unquotedNextStateTokens;
2224
private final List<Tokenizer.Token<TokenType>> quotedNextStateTokens;
2325

@@ -29,23 +31,34 @@ public class CsvSharpLexer extends LexerBase {
2931
private IElementType currentTokenType;
3032
private boolean failed;
3133

34+
private static final Map<TokenType, LexerState> INITIAL_NEXT_STATES = new HashMap<>();
3235
private static final Map<TokenType, LexerState> UNQUOTED_NEXT_STATES = new HashMap<>();
3336
private static final Map<TokenType, LexerState> QUOTED_NEXT_STATES = new HashMap<>();
3437

3538
static {
39+
INITIAL_NEXT_STATES.put(TokenType.WHITESPACE, LexerState.Initial);
40+
INITIAL_NEXT_STATES.put(TokenType.TEXT, LexerState.Unquoted);
41+
INITIAL_NEXT_STATES.put(TokenType.VALUE_SEPARATOR, LexerState.Unquoted);
42+
INITIAL_NEXT_STATES.put(TokenType.BEGIN_QUOTE, LexerState.Quoted);
43+
INITIAL_NEXT_STATES.put(TokenType.RECORD_SEPARATOR, LexerState.Initial);
44+
INITIAL_NEXT_STATES.put(TokenType.COMMENT, LexerState.Initial);
45+
3646
UNQUOTED_NEXT_STATES.put(TokenType.WHITESPACE, LexerState.Unquoted);
3747
UNQUOTED_NEXT_STATES.put(TokenType.TEXT, LexerState.Unquoted);
48+
UNQUOTED_NEXT_STATES.put(TokenType.COMMENT_CHARACTER, LexerState.Unquoted);
3849
UNQUOTED_NEXT_STATES.put(TokenType.VALUE_SEPARATOR, LexerState.Unquoted);
39-
UNQUOTED_NEXT_STATES.put(TokenType.RECORD_SEPARATOR, LexerState.Unquoted);
4050
UNQUOTED_NEXT_STATES.put(TokenType.BEGIN_QUOTE, LexerState.Quoted);
51+
UNQUOTED_NEXT_STATES.put(TokenType.RECORD_SEPARATOR, LexerState.Initial);
4152

4253
QUOTED_NEXT_STATES.put(TokenType.WHITESPACE, LexerState.Quoted);
4354
QUOTED_NEXT_STATES.put(TokenType.TEXT, LexerState.Quoted);
55+
QUOTED_NEXT_STATES.put(TokenType.COMMENT_CHARACTER, LexerState.Quoted);
4456
QUOTED_NEXT_STATES.put(TokenType.ESCAPED_CHARACTER, LexerState.Quoted);
4557
QUOTED_NEXT_STATES.put(TokenType.END_QUOTE, LexerState.Unquoted);
4658
}
4759

4860
enum LexerState {
61+
Initial(INITIAL_NEXT_STATES),
4962
Unquoted(UNQUOTED_NEXT_STATES),
5063
Quoted(QUOTED_NEXT_STATES);
5164

@@ -71,22 +84,26 @@ enum TokenType {
7184
ESCAPED_CHARACTER,
7285
VALUE_SEPARATOR,
7386
RECORD_SEPARATOR,
74-
WHITESPACE
87+
WHITESPACE,
88+
COMMENT,
89+
COMMENT_CHARACTER
7590
}
7691

7792
public static class Configuration {
78-
public static final Configuration DEFAULT = new Configuration(",", "\n", "\"", "\"");
93+
public static final Configuration DEFAULT = new Configuration(",", "\n", "\"", "\"", "#");
7994

8095
public String valueSeparator;
8196
public String recordSeparator;
8297
public String escapeCharacter;
8398
public String quoteCharacter;
99+
public String commentCharacter;
84100

85-
public Configuration(String valueSeparator, String recordSeparator, String escapeCharacter, String quoteCharacter) {
101+
public Configuration(String valueSeparator, String recordSeparator, String escapeCharacter, String quoteCharacter, String commentCharacter) {
86102
this.valueSeparator = Pattern.quote(valueSeparator);
87103
this.recordSeparator = Pattern.quote(recordSeparator);
88104
this.escapeCharacter = Pattern.quote(escapeCharacter);
89105
this.quoteCharacter = Pattern.quote(quoteCharacter);
106+
this.commentCharacter = Pattern.quote(commentCharacter);
90107
}
91108
}
92109

@@ -102,17 +119,32 @@ public CsvSharpLexer(Configuration configuration) {
102119
tokenizer.add(TokenType.BEGIN_QUOTE, String.format("%s", configuration.quoteCharacter));
103120
tokenizer.add(TokenType.VALUE_SEPARATOR, configuration.valueSeparator);
104121
tokenizer.add(TokenType.RECORD_SEPARATOR, configuration.recordSeparator);
122+
if (!configuration.commentCharacter.isEmpty()) {
123+
tokenizer.add(TokenType.COMMENT_CHARACTER, configuration.commentCharacter);
124+
tokenizer.add(TokenType.COMMENT, configuration.commentCharacter + ".*(?=(\n|$))");
125+
}
105126

106127
if (configuration.escapeCharacter.equals(configuration.quoteCharacter)) {
107128
tokenizer.add(TokenType.END_QUOTE, String.format("%s(?!%s)", configuration.quoteCharacter, configuration.quoteCharacter));
108129
tokenizer.add(TokenType.ESCAPED_CHARACTER, String.format("(%s%s|%s|%s)+", configuration.quoteCharacter, configuration.quoteCharacter, configuration.valueSeparator, configuration.recordSeparator));
109-
tokenizer.add(TokenType.TEXT, String.format("((?!%s)[^ \f%s%s])+", configuration.valueSeparator, configuration.quoteCharacter, configuration.recordSeparator));
130+
if (!configuration.commentCharacter.isEmpty()) {
131+
tokenizer.add(TokenType.TEXT, String.format("((?!(%s|%s))[^ \f%s%s])+", configuration.commentCharacter, configuration.valueSeparator, configuration.quoteCharacter, configuration.recordSeparator));
132+
} else {
133+
tokenizer.add(TokenType.TEXT, String.format("((?!%s)[^ \f%s%s])+", configuration.valueSeparator, configuration.quoteCharacter, configuration.recordSeparator));
134+
}
110135
} else {
111136
tokenizer.add(TokenType.END_QUOTE, String.format("%s", configuration.quoteCharacter));
112137
tokenizer.add(TokenType.ESCAPED_CHARACTER, String.format("(%s%s|%s%s|%s|%s)+", configuration.escapeCharacter, configuration.quoteCharacter, configuration.escapeCharacter, configuration.escapeCharacter, configuration.valueSeparator, configuration.recordSeparator));
113-
tokenizer.add(TokenType.TEXT, String.format("((?!%s)[^ \f%s%s%s])+", configuration.valueSeparator, configuration.escapeCharacter, configuration.quoteCharacter, configuration.recordSeparator));
138+
if (!configuration.commentCharacter.isEmpty()) {
139+
tokenizer.add(TokenType.TEXT, String.format("((?!(%s|%s))[^ \f%s%s%s])+", configuration.commentCharacter, configuration.valueSeparator, configuration.escapeCharacter, configuration.quoteCharacter, configuration.recordSeparator));
140+
} else {
141+
tokenizer.add(TokenType.TEXT, String.format("((?!%s)[^ \f%s%s%s])+", configuration.valueSeparator, configuration.escapeCharacter, configuration.quoteCharacter, configuration.recordSeparator));
142+
}
114143
}
115144

145+
initialNextStateTokens = LexerState.Initial.getPossibleTokens().stream()
146+
.map(tokenizer::getToken)
147+
.collect(Collectors.toList());
116148
unquotedNextStateTokens = LexerState.Unquoted.getPossibleTokens().stream()
117149
.map(tokenizer::getToken)
118150
.collect(Collectors.toList());
@@ -126,14 +158,14 @@ public void start(@NotNull CharSequence buffer, int startOffset, int endOffset,
126158
this.buffer = buffer;
127159
this.tokenStart = this.tokenEnd = startOffset;
128160
this.bufferEnd = endOffset;
129-
this.currentState = initialState == 0 ? LexerState.Unquoted : LexerState.Quoted;
161+
this.currentState = LexerState.values()[initialState];
130162
this.currentTokenType = null;
131163
}
132164

133165
@Override
134166
public int getState() {
135167
locateToken();
136-
return currentState == LexerState.Unquoted ? 0 : 1;
168+
return currentState.ordinal();
137169
}
138170

139171
@Nullable
@@ -178,6 +210,19 @@ protected void raiseFailure() {
178210
tokenEnd = bufferEnd;
179211
}
180212

213+
protected Collection<Tokenizer.Token<TokenType>> getCurrentTokenCollection() {
214+
switch(this.currentState) {
215+
case Initial:
216+
return initialNextStateTokens;
217+
case Unquoted:
218+
return unquotedNextStateTokens;
219+
case Quoted:
220+
return quotedNextStateTokens;
221+
default:
222+
throw new UnhandledSwitchCaseException(this.currentState);
223+
}
224+
}
225+
181226
protected synchronized void locateToken() {
182227
if (currentTokenType != null) {
183228
return;
@@ -193,7 +238,7 @@ protected synchronized void locateToken() {
193238
tokenizer.findToken(buffer,
194239
tokenStart,
195240
bufferEnd,
196-
currentState == LexerState.Unquoted ? unquotedNextStateTokens : quotedNextStateTokens,
241+
getCurrentTokenCollection(),
197242
null
198243
);
199244

@@ -222,8 +267,12 @@ protected synchronized void locateToken() {
222267
currentTokenType = CsvTypes.COMMA;
223268
break;
224269
case TEXT:
270+
case COMMENT_CHARACTER:
225271
currentTokenType = CsvTypes.TEXT;
226272
break;
273+
case COMMENT:
274+
currentTokenType = CsvTypes.COMMENT;
275+
break;
227276
case WHITESPACE:
228277
currentTokenType = com.intellij.psi.TokenType.WHITE_SPACE;
229278
break;

src/main/java/net/seesharpsoft/intellij/plugins/csv/editor/table/CsvTableEditor.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ public void setEditable(boolean editable) {
102102
}
103103

104104
public boolean isEditable() {
105-
return this.tableIsEditable && !this.hasErrors();
105+
return this.tableIsEditable && !this.hasErrors() && !hasComments();
106106
}
107107

108108
public CsvColumnInfoMap<PsiElement> getColumnInfoMap() {
@@ -118,6 +118,14 @@ public boolean hasErrors() {
118118
return (columnInfoMap != null && columnInfoMap.hasErrors());
119119
}
120120

121+
public boolean hasComments() {
122+
if (!isValid()) {
123+
return false;
124+
}
125+
CsvColumnInfoMap columnInfoMap = getColumnInfoMap();
126+
return (columnInfoMap != null && columnInfoMap.hasComments());
127+
}
128+
121129
protected Object[][] storeStateChange(Object[][] data) {
122130
Object[][] result = this.dataManagement.addState(data);
123131
saveChanges();

src/main/java/net/seesharpsoft/intellij/plugins/csv/highlighter/CsvSyntaxHighlighter.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ public class CsvSyntaxHighlighter extends SyntaxHighlighterBase {
2424
createTextAttributesKey("CSV_DEFAULT_STRING", DefaultLanguageHighlighterColors.STRING);
2525
public static final TextAttributesKey ESCAPED_TEXT =
2626
createTextAttributesKey("CSV_ESCAPED_STRING", DefaultLanguageHighlighterColors.VALID_STRING_ESCAPE);
27+
public static final TextAttributesKey COMMENT =
28+
createTextAttributesKey("CSV_DEFAULT_COMMENT", DefaultLanguageHighlighterColors.LINE_COMMENT);
2729
public static final TextAttributesKey BAD_CHARACTER =
2830
createTextAttributesKey("CSV_BAD_CHARACTER", HighlighterColors.BAD_CHARACTER);
2931

@@ -32,6 +34,7 @@ public class CsvSyntaxHighlighter extends SyntaxHighlighterBase {
3234
private static final TextAttributesKey[] QUOTE_KEYS = new TextAttributesKey[] {QUOTE};
3335
private static final TextAttributesKey[] TEXT_KEYS = new TextAttributesKey[] {TEXT};
3436
private static final TextAttributesKey[] ESCAPED_TEXT_KEYS = new TextAttributesKey[] {ESCAPED_TEXT};
37+
private static final TextAttributesKey[] COMMENT_KEYS = new TextAttributesKey[] {COMMENT};
3538
private static final TextAttributesKey[] EMPTY_KEYS = new TextAttributesKey[0];
3639

3740
private final Project myProject;
@@ -57,6 +60,8 @@ public TextAttributesKey[] getTokenHighlights(IElementType tokenType) {
5760
return QUOTE_KEYS;
5861
} else if (tokenType.equals(CsvTypes.TEXT)) {
5962
return TEXT_KEYS;
63+
} else if (tokenType.equals(CsvTypes.COMMENT)) {
64+
return COMMENT_KEYS;
6065
} else if (tokenType.equals(CsvTypes.ESCAPED_TEXT)) {
6166
return ESCAPED_TEXT_KEYS;
6267
} else if (tokenType.equals(TokenType.BAD_CHARACTER)) {

src/main/java/net/seesharpsoft/intellij/plugins/csv/psi/CsvFile.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public void propertyChange(PropertyChangeEvent evt) {
2727
switch (evt.getPropertyName()) {
2828
case "defaultEscapeCharacter":
2929
case "defaultValueSeparator":
30+
case "commentIndicator":
3031
FileContentUtilCore.reparseFiles(CsvFile.this.getVirtualFile());
3132
break;
3233
default:

src/main/java/net/seesharpsoft/intellij/plugins/csv/settings/CsvColorSettings.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ public class CsvColorSettings implements ColorSettingsPage {
3636
attributesDescriptors.add(new AttributesDescriptor("Quote", CsvSyntaxHighlighter.QUOTE));
3737
attributesDescriptors.add(new AttributesDescriptor("Text", CsvSyntaxHighlighter.TEXT));
3838
attributesDescriptors.add(new AttributesDescriptor("Escaped Text", CsvSyntaxHighlighter.ESCAPED_TEXT));
39+
attributesDescriptors.add(new AttributesDescriptor("Comment", CsvSyntaxHighlighter.COMMENT));
3940

4041
COLUMN_HIGHLIGHT_ATTRIBUTES = new ArrayList<>();
4142
for (int i = 0; i < MAX_COLUMN_HIGHLIGHT_COLORS; ++i) {

src/main/java/net/seesharpsoft/intellij/plugins/csv/settings/CsvEditorSettings.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ public class CsvEditorSettings implements PersistentStateComponent<CsvEditorSett
3232
public static final CsvEscapeCharacter ESCAPE_CHARACTER_DEFAULT = CsvEscapeCharacter.QUOTE;
3333
public static final CsvValueSeparator VALUE_SEPARATOR_DEFAULT = CsvValueSeparator.COMMA;
3434

35+
public static final String COMMENT_INDICATOR_DEFAULT = "#";
36+
3537
private static final CsvEditorSettings STATIC_INSTANCE = new CsvEditorSettings();
3638

3739
public enum EditorPrio {
@@ -63,6 +65,7 @@ public static final class OptionSet {
6365
@OptionTag(converter = CsvValueSeparator.CsvValueSeparatorConverter.class)
6466
public CsvValueSeparator DEFAULT_VALUE_SEPARATOR = VALUE_SEPARATOR_DEFAULT;
6567
public boolean KEEP_TRAILING_SPACES = false;
68+
public String COMMENT_INDICATOR = COMMENT_INDICATOR_DEFAULT;
6669

6770
public OptionSet() {
6871
EditorSettingsExternalizable editorSettingsExternalizable = EditorSettingsExternalizable.getInstance();
@@ -271,4 +274,16 @@ public void setKeepTrailingSpaces(boolean keepTrailingSpaces) {
271274
public boolean getKeepTrailingSpaces() {
272275
return getState().KEEP_TRAILING_SPACES;
273276
}
277+
278+
public void setCommentIndicator(String commentIndicator) {
279+
String oldValue = getCommentIndicator();
280+
getState().COMMENT_INDICATOR = commentIndicator.trim();
281+
if (commentIndicator != oldValue) {
282+
myPropertyChangeSupport.firePropertyChange("commentIndicator", oldValue, getCommentIndicator());
283+
}
284+
}
285+
286+
public String getCommentIndicator() {
287+
return getState().COMMENT_INDICATOR;
288+
}
274289
}

0 commit comments

Comments
 (0)