Skip to content

Commit e28361b

Browse files
authored
Merge pull request #126 from SeeSharpSoft/fb_file_based_separator
File based separator setting
2 parents 80b20c5 + ce5d159 commit e28361b

27 files changed

+481
-46
lines changed

CHANGELOG

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ May 7, 2019
33

44
NEW: option to keep/ignore a linebreak at the end of a file (table editor)
55
NEW: improved change detection of table editor to avoid overwriting original text representation without editing any values
6+
NEW: file based value separator (e.g. ',' or ';')
67

78
2.3.1
89
Mar 31, 2019

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,8 @@ Annasusanna,Amsterdam,1
207207

208208
The following separators are currently supported: **,** (Comma), **;** (Semicolon), **|** (Pipe) and **↹** (Tab)
209209

210+
_Value separator (default)_ defines which separator is used by default. The separator character can be changed for each CSV file individually.
211+
210212
When changing the separator, press the apply button to refresh the preview window properly.
211213

212214
_Space before separator_
@@ -279,6 +281,18 @@ Annasusanna,Amsterdam, 1
279281
Ben, Berlin, 2
280282
```
281283

284+
### Actions
285+
286+
#### File specific value separator
287+
288+
![Context menu](./docs/contextmenu.png)
289+
290+
The action to switch the value separator used for CSV syntax validation of a specific file is part of its text editors context menu.
291+
292+
293+
This action defines how the parser/validator/highlighter/etc. behaves. It does intentionally not change the file content.
294+
To be more precise: It **does not replace** previous separator characters by new ones or adjust the escaped texts.
295+
282296
### Inspections
283297

284298
- _File > Settings > Editor > Inspections > CSV_

docs/contextmenu.png

10.1 KB
Loading
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package net.seesharpsoft.intellij.lang;
2+
3+
import com.intellij.lang.ParserDefinition;
4+
import com.intellij.lang.PsiParser;
5+
import com.intellij.lexer.Lexer;
6+
import com.intellij.psi.PsiFile;
7+
8+
/**
9+
* Support for file specific parser definition.
10+
*/
11+
public interface FileParserDefinition extends ParserDefinition {
12+
default Lexer createLexer(PsiFile file) {
13+
return createLexer(file.getProject());
14+
}
15+
16+
default PsiParser createParser(PsiFile file) {
17+
return createParser(file.getProject());
18+
}
19+
}

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@
88
import com.intellij.openapi.project.Project;
99
import com.intellij.openapi.vfs.VirtualFile;
1010
import com.intellij.psi.PsiElement;
11+
import com.intellij.psi.PsiFile;
1112
import com.intellij.psi.PsiManager;
1213
import com.intellij.psi.TokenType;
1314
import com.intellij.psi.impl.source.DummyHolder;
1415
import com.intellij.psi.impl.source.DummyHolderFactory;
1516
import com.intellij.psi.impl.source.tree.FileElement;
1617
import com.intellij.psi.tree.IElementType;
1718
import com.intellij.psi.util.PsiTreeUtil;
19+
import net.seesharpsoft.intellij.lang.FileParserDefinition;
1820
import net.seesharpsoft.intellij.plugins.csv.psi.CsvField;
1921
import net.seesharpsoft.intellij.plugins.csv.psi.CsvFile;
2022
import net.seesharpsoft.intellij.plugins.csv.psi.CsvRecord;
@@ -27,14 +29,15 @@ public final class CsvHelper {
2729

2830
// replaces PsiElementFactory.SERVICE.getInstance(element.getProject()).createDummyHolder("<undefined>", CsvTypes.FIELD, null);
2931
// https://github.com/SeeSharpSoft/intellij-csv-validator/issues/4
30-
public static PsiElement createEmptyCsvField(Project project) {
32+
public static PsiElement createEmptyCsvField(PsiFile psiFile) {
33+
final Project project = psiFile.getProject();
3134
final String text = "<undefined>";
3235
final IElementType type = CsvTypes.FIELD;
3336
final PsiManager psiManager = PsiManager.getInstance(project);
3437
final DummyHolder dummyHolder = DummyHolderFactory.createHolder(psiManager, null);
3538
final FileElement fileElement = dummyHolder.getTreeElement();
36-
final ParserDefinition parserDefinition = LanguageParserDefinitions.INSTANCE.forLanguage(CsvLanguage.INSTANCE);
37-
final Lexer lexer = parserDefinition.createLexer(project);
39+
final FileParserDefinition parserDefinition = (FileParserDefinition)LanguageParserDefinitions.INSTANCE.forLanguage(CsvLanguage.INSTANCE);
40+
final Lexer lexer = parserDefinition.createLexer(psiFile);
3841
final PsiBuilder psiBuilder = PsiBuilderFactory.getInstance().createBuilder(project, fileElement, lexer, CsvLanguage.INSTANCE, text);
3942
final ASTNode node = parserDefinition.createParser(project).parse(type, psiBuilder);
4043
fileElement.rawAddChildren((com.intellij.psi.impl.source.tree.TreeElement) node);
@@ -44,7 +47,7 @@ public static PsiElement createEmptyCsvField(Project project) {
4447
public static boolean isCsvFile(Project project, VirtualFile file) {
4548
final FileType fileType = file.getFileType();
4649
return (fileType instanceof LanguageFileType && ((LanguageFileType) fileType).getLanguage().isKindOf(CsvLanguage.INSTANCE)) ||
47-
(fileType == ScratchFileType.INSTANCE && LanguageUtil.getLanguageForPsi(project, file) == CsvLanguage.INSTANCE);
50+
(fileType == ScratchFileType.INSTANCE && LanguageUtil.getLanguageForPsi(project, file).isKindOf(CsvLanguage.INSTANCE));
4851
}
4952

5053
public static IElementType getElementType(PsiElement element) {

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

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package net.seesharpsoft.intellij.plugins.csv;
22

33
import com.intellij.lang.ASTNode;
4-
import com.intellij.lang.ParserDefinition;
54
import com.intellij.lang.PsiParser;
65
import com.intellij.lexer.Lexer;
76
import com.intellij.openapi.project.Project;
@@ -11,21 +10,23 @@
1110
import com.intellij.psi.TokenType;
1211
import com.intellij.psi.tree.IFileElementType;
1312
import com.intellij.psi.tree.TokenSet;
13+
import net.seesharpsoft.intellij.lang.FileParserDefinition;
1414
import net.seesharpsoft.intellij.plugins.csv.parser.CsvParser;
1515
import net.seesharpsoft.intellij.plugins.csv.psi.CsvFile;
16+
import net.seesharpsoft.intellij.plugins.csv.psi.CsvFileElementType;
1617
import net.seesharpsoft.intellij.plugins.csv.psi.CsvTypes;
1718
import net.seesharpsoft.intellij.plugins.csv.settings.CsvCodeStyleSettings;
1819
import org.jetbrains.annotations.NotNull;
1920

20-
public class CsvParserDefinition implements ParserDefinition {
21+
public class CsvParserDefinition implements FileParserDefinition {
2122
public static final TokenSet WHITE_SPACES = TokenSet.create(TokenType.WHITE_SPACE);
2223

23-
public static final IFileElementType FILE = new IFileElementType(CsvLanguage.INSTANCE);
24+
public static final IFileElementType FILE = new CsvFileElementType(CsvLanguage.INSTANCE);
2425

2526
@NotNull
2627
@Override
2728
public Lexer createLexer(Project project) {
28-
return new CsvLexerAdapter(CsvCodeStyleSettings.getCurrentSeparator(project));
29+
throw new UnsupportedOperationException("use 'createLexer(PsiFile file)' instead");
2930
}
3031

3132
@Override
@@ -72,4 +73,14 @@ public SpaceRequirements spaceExistanceTypeBetweenTokens(ASTNode left, ASTNode r
7273
public PsiElement createElement(ASTNode node) {
7374
return CsvTypes.Factory.createElement(node);
7475
}
76+
77+
@Override
78+
public Lexer createLexer(@NotNull PsiFile file) {
79+
return new CsvLexerAdapter(CsvCodeStyleSettings.getCurrentSeparator(file));
80+
}
81+
82+
@Override
83+
public PsiParser createParser(@NotNull PsiFile file) {
84+
return createParser(file.getProject());
85+
}
7586
}

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,11 @@ private CsvParserUtil() {
1515
public static boolean separator(PsiBuilder builder, int tokenType) {
1616
if (builder.getTokenType() == CsvTypes.COMMA) {
1717
PsiFile currentFile = builder.getUserDataUnprotected(FileContextUtil.CONTAINING_FILE_KEY);
18+
if (currentFile == null) {
19+
throw new UnsupportedOperationException("parser requires containing file");
20+
}
1821
return builder.getTokenText().equals(
19-
CsvCodeStyleSettings.getCurrentSeparator(builder.getProject(), currentFile != null ? currentFile.getLanguage() : null)
22+
CsvCodeStyleSettings.getCurrentSeparator(currentFile)
2023
);
2124
}
2225
return false;
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package net.seesharpsoft.intellij.plugins.csv;
2+
3+
import com.intellij.openapi.util.Key;
4+
import com.intellij.psi.PsiFile;
5+
import com.intellij.util.PathUtil;
6+
import org.jetbrains.annotations.NotNull;
7+
8+
import java.util.regex.Pattern;
9+
10+
public final class CsvStorageHelper {
11+
public static final String CSV_STATE_STORAGE_FILE = "csv-plugin.xml";
12+
13+
public static final Key<String> RELATIVE_FILE_URL = Key.create("CSV_PLUGIN_RELATIVE_URL");
14+
15+
public static String getRelativeFileUrl(@NotNull PsiFile psiFile) {
16+
String url = psiFile.getUserData(RELATIVE_FILE_URL);
17+
if (url == null) {
18+
String projectDir = PathUtil.getLocalPath(psiFile.getProject().getBasePath());
19+
url = PathUtil.getLocalPath(psiFile.getOriginalFile().getVirtualFile().getPath())
20+
.replaceFirst("^" + Pattern.quote(projectDir), "");
21+
psiFile.putUserData(RELATIVE_FILE_URL, url);
22+
}
23+
return url;
24+
}
25+
26+
private CsvStorageHelper() {
27+
// static
28+
}
29+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package net.seesharpsoft.intellij.plugins.csv.actions;
2+
3+
import com.intellij.lang.Language;
4+
import com.intellij.openapi.actionSystem.AnActionEvent;
5+
import com.intellij.openapi.actionSystem.CommonDataKeys;
6+
import com.intellij.openapi.actionSystem.ToggleAction;
7+
import com.intellij.openapi.components.ServiceManager;
8+
import com.intellij.psi.PsiFile;
9+
import com.intellij.util.FileContentUtil;
10+
import net.seesharpsoft.intellij.plugins.csv.CsvLanguage;
11+
import net.seesharpsoft.intellij.plugins.csv.CsvSeparatorHolder;
12+
import net.seesharpsoft.intellij.plugins.csv.components.CsvFileAttributes;
13+
import net.seesharpsoft.intellij.plugins.csv.settings.CsvCodeStyleSettings;
14+
import org.jetbrains.annotations.NotNull;
15+
16+
public class CsvChangeSeparatorAction extends ToggleAction {
17+
private String mySeparator;
18+
19+
CsvChangeSeparatorAction(String separator, String mySeparatorTextArg) {
20+
super(mySeparatorTextArg);
21+
mySeparator = separator;
22+
}
23+
24+
@Override
25+
public boolean isSelected(@NotNull AnActionEvent anActionEvent) {
26+
PsiFile psiFile = anActionEvent.getData(CommonDataKeys.PSI_FILE);
27+
if (psiFile == null) {
28+
return false;
29+
}
30+
CsvFileAttributes csvFileAttributes = ServiceManager.getService(psiFile.getProject(), CsvFileAttributes.class);
31+
return csvFileAttributes.getFileSeparator(psiFile) != null && CsvCodeStyleSettings.getCurrentSeparator(psiFile).equals(mySeparator);
32+
}
33+
34+
@Override
35+
public void setSelected(@NotNull AnActionEvent anActionEvent, boolean b) {
36+
PsiFile psiFile = anActionEvent.getData(CommonDataKeys.PSI_FILE);
37+
if (psiFile == null) {
38+
return;
39+
}
40+
Language language = psiFile.getLanguage();
41+
if (!language.isKindOf(CsvLanguage.INSTANCE) || language instanceof CsvSeparatorHolder) {
42+
return;
43+
}
44+
CsvFileAttributes csvFileAttributes = ServiceManager.getService(psiFile.getProject(), CsvFileAttributes.class);
45+
csvFileAttributes.setFileSeparator(psiFile, this.mySeparator);
46+
FileContentUtil.reparseFiles(psiFile.getVirtualFile());
47+
}
48+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package net.seesharpsoft.intellij.plugins.csv.actions;
2+
3+
import com.intellij.lang.Language;
4+
import com.intellij.openapi.actionSystem.ActionGroup;
5+
import com.intellij.openapi.actionSystem.AnAction;
6+
import com.intellij.openapi.actionSystem.AnActionEvent;
7+
import com.intellij.openapi.actionSystem.CommonDataKeys;
8+
import com.intellij.psi.PsiFile;
9+
import net.seesharpsoft.intellij.plugins.csv.CsvLanguage;
10+
import net.seesharpsoft.intellij.plugins.csv.CsvSeparatorHolder;
11+
import net.seesharpsoft.intellij.plugins.csv.settings.CsvCodeStyleSettings;
12+
import org.jetbrains.annotations.NotNull;
13+
import org.jetbrains.annotations.Nullable;
14+
15+
public class CsvChangeSeparatorActionGroup extends ActionGroup {
16+
17+
private static final AnAction[] CSV_SEPARATOR_CHANGE_ACTIONS;
18+
19+
static {
20+
CSV_SEPARATOR_CHANGE_ACTIONS = new AnAction[CsvCodeStyleSettings.SUPPORTED_SEPARATORS.length + 1];
21+
for (int i = 0; i < CSV_SEPARATOR_CHANGE_ACTIONS.length - 1; ++i) {
22+
CSV_SEPARATOR_CHANGE_ACTIONS[i] = new CsvChangeSeparatorAction(CsvCodeStyleSettings.SUPPORTED_SEPARATORS[i], CsvCodeStyleSettings.SUPPORTED_SEPARATORS_DISPLAY[i]);
23+
}
24+
CSV_SEPARATOR_CHANGE_ACTIONS[CSV_SEPARATOR_CHANGE_ACTIONS.length - 1] = new CsvDefaultSeparatorAction();
25+
}
26+
27+
@Override
28+
public void update(AnActionEvent anActionEvent) {
29+
PsiFile psiFile = anActionEvent.getData(CommonDataKeys.PSI_FILE);
30+
Language language = psiFile == null ? null : psiFile.getLanguage();
31+
anActionEvent.getPresentation().setEnabledAndVisible(psiFile != null && language != null &&
32+
language.isKindOf(CsvLanguage.INSTANCE) && !(language instanceof CsvSeparatorHolder)
33+
);
34+
35+
if (psiFile != null) {
36+
anActionEvent.getPresentation()
37+
.setText(String.format("CSV Value Separator: %s", CsvCodeStyleSettings.getSeparatorDisplayText(CsvCodeStyleSettings.getCurrentSeparator(psiFile))));
38+
}
39+
}
40+
41+
@NotNull
42+
@Override
43+
public AnAction[] getChildren(@Nullable AnActionEvent anActionEvent) {
44+
return CSV_SEPARATOR_CHANGE_ACTIONS;
45+
}
46+
}

0 commit comments

Comments
 (0)