Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ May 7, 2019

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

2.3.1
Mar 31, 2019
Expand Down
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,8 @@ Annasusanna,Amsterdam,1

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

_Value separator (default)_ defines which separator is used by default. The separator character can be changed for each CSV file individually.

When changing the separator, press the apply button to refresh the preview window properly.

_Space before separator_
Expand Down Expand Up @@ -279,6 +281,18 @@ Annasusanna,Amsterdam, 1
Ben, Berlin, 2
```

### Actions

#### File specific value separator

![Context menu](./docs/contextmenu.png)

The action to switch the value separator used for CSV syntax validation of a specific file is part of its text editors context menu.


This action defines how the parser/validator/highlighter/etc. behaves. It does intentionally not change the file content.
To be more precise: It **does not replace** previous separator characters by new ones or adjust the escaped texts.

### Inspections

- _File > Settings > Editor > Inspections > CSV_
Expand Down
Binary file added docs/contextmenu.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package net.seesharpsoft.intellij.lang;

import com.intellij.lang.ParserDefinition;
import com.intellij.lang.PsiParser;
import com.intellij.lexer.Lexer;
import com.intellij.psi.PsiFile;

/**
* Support for file specific parser definition.
*/
public interface FileParserDefinition extends ParserDefinition {
default Lexer createLexer(PsiFile file) {
return createLexer(file.getProject());
}

default PsiParser createParser(PsiFile file) {
return createParser(file.getProject());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.psi.TokenType;
import com.intellij.psi.impl.source.DummyHolder;
import com.intellij.psi.impl.source.DummyHolderFactory;
import com.intellij.psi.impl.source.tree.FileElement;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiTreeUtil;
import net.seesharpsoft.intellij.lang.FileParserDefinition;
import net.seesharpsoft.intellij.plugins.csv.psi.CsvField;
import net.seesharpsoft.intellij.plugins.csv.psi.CsvFile;
import net.seesharpsoft.intellij.plugins.csv.psi.CsvRecord;
Expand All @@ -27,14 +29,15 @@ public final class CsvHelper {

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

public static IElementType getElementType(PsiElement element) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package net.seesharpsoft.intellij.plugins.csv;

import com.intellij.lang.ASTNode;
import com.intellij.lang.ParserDefinition;
import com.intellij.lang.PsiParser;
import com.intellij.lexer.Lexer;
import com.intellij.openapi.project.Project;
Expand All @@ -11,21 +10,23 @@
import com.intellij.psi.TokenType;
import com.intellij.psi.tree.IFileElementType;
import com.intellij.psi.tree.TokenSet;
import net.seesharpsoft.intellij.lang.FileParserDefinition;
import net.seesharpsoft.intellij.plugins.csv.parser.CsvParser;
import net.seesharpsoft.intellij.plugins.csv.psi.CsvFile;
import net.seesharpsoft.intellij.plugins.csv.psi.CsvFileElementType;
import net.seesharpsoft.intellij.plugins.csv.psi.CsvTypes;
import net.seesharpsoft.intellij.plugins.csv.settings.CsvCodeStyleSettings;
import org.jetbrains.annotations.NotNull;

public class CsvParserDefinition implements ParserDefinition {
public class CsvParserDefinition implements FileParserDefinition {
public static final TokenSet WHITE_SPACES = TokenSet.create(TokenType.WHITE_SPACE);

public static final IFileElementType FILE = new IFileElementType(CsvLanguage.INSTANCE);
public static final IFileElementType FILE = new CsvFileElementType(CsvLanguage.INSTANCE);

@NotNull
@Override
public Lexer createLexer(Project project) {
return new CsvLexerAdapter(CsvCodeStyleSettings.getCurrentSeparator(project));
throw new UnsupportedOperationException("use 'createLexer(PsiFile file)' instead");
}

@Override
Expand Down Expand Up @@ -72,4 +73,14 @@ public SpaceRequirements spaceExistanceTypeBetweenTokens(ASTNode left, ASTNode r
public PsiElement createElement(ASTNode node) {
return CsvTypes.Factory.createElement(node);
}

@Override
public Lexer createLexer(@NotNull PsiFile file) {
return new CsvLexerAdapter(CsvCodeStyleSettings.getCurrentSeparator(file));
}

@Override
public PsiParser createParser(@NotNull PsiFile file) {
return createParser(file.getProject());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@ private CsvParserUtil() {
public static boolean separator(PsiBuilder builder, int tokenType) {
if (builder.getTokenType() == CsvTypes.COMMA) {
PsiFile currentFile = builder.getUserDataUnprotected(FileContextUtil.CONTAINING_FILE_KEY);
if (currentFile == null) {
throw new UnsupportedOperationException("parser requires containing file");
}
return builder.getTokenText().equals(
CsvCodeStyleSettings.getCurrentSeparator(builder.getProject(), currentFile != null ? currentFile.getLanguage() : null)
CsvCodeStyleSettings.getCurrentSeparator(currentFile)
);
}
return false;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package net.seesharpsoft.intellij.plugins.csv;

import com.intellij.openapi.util.Key;
import com.intellij.psi.PsiFile;
import com.intellij.util.PathUtil;
import org.jetbrains.annotations.NotNull;

import java.util.regex.Pattern;

public final class CsvStorageHelper {
public static final String CSV_STATE_STORAGE_FILE = "csv-plugin.xml";

public static final Key<String> RELATIVE_FILE_URL = Key.create("CSV_PLUGIN_RELATIVE_URL");

public static String getRelativeFileUrl(@NotNull PsiFile psiFile) {
String url = psiFile.getUserData(RELATIVE_FILE_URL);
if (url == null) {
String projectDir = PathUtil.getLocalPath(psiFile.getProject().getBasePath());
url = PathUtil.getLocalPath(psiFile.getOriginalFile().getVirtualFile().getPath())
.replaceFirst("^" + Pattern.quote(projectDir), "");
psiFile.putUserData(RELATIVE_FILE_URL, url);
}
return url;
}

private CsvStorageHelper() {
// static
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package net.seesharpsoft.intellij.plugins.csv.actions;

import com.intellij.lang.Language;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.ToggleAction;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.psi.PsiFile;
import com.intellij.util.FileContentUtil;
import net.seesharpsoft.intellij.plugins.csv.CsvLanguage;
import net.seesharpsoft.intellij.plugins.csv.CsvSeparatorHolder;
import net.seesharpsoft.intellij.plugins.csv.components.CsvFileAttributes;
import net.seesharpsoft.intellij.plugins.csv.settings.CsvCodeStyleSettings;
import org.jetbrains.annotations.NotNull;

public class CsvChangeSeparatorAction extends ToggleAction {
private String mySeparator;

CsvChangeSeparatorAction(String separator, String mySeparatorTextArg) {
super(mySeparatorTextArg);
mySeparator = separator;
}

@Override
public boolean isSelected(@NotNull AnActionEvent anActionEvent) {
PsiFile psiFile = anActionEvent.getData(CommonDataKeys.PSI_FILE);
if (psiFile == null) {
return false;
}
CsvFileAttributes csvFileAttributes = ServiceManager.getService(psiFile.getProject(), CsvFileAttributes.class);
return csvFileAttributes.getFileSeparator(psiFile) != null && CsvCodeStyleSettings.getCurrentSeparator(psiFile).equals(mySeparator);
}

@Override
public void setSelected(@NotNull AnActionEvent anActionEvent, boolean b) {
PsiFile psiFile = anActionEvent.getData(CommonDataKeys.PSI_FILE);
if (psiFile == null) {
return;
}
Language language = psiFile.getLanguage();
if (!language.isKindOf(CsvLanguage.INSTANCE) || language instanceof CsvSeparatorHolder) {
return;
}
CsvFileAttributes csvFileAttributes = ServiceManager.getService(psiFile.getProject(), CsvFileAttributes.class);
csvFileAttributes.setFileSeparator(psiFile, this.mySeparator);
FileContentUtil.reparseFiles(psiFile.getVirtualFile());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package net.seesharpsoft.intellij.plugins.csv.actions;

import com.intellij.lang.Language;
import com.intellij.openapi.actionSystem.ActionGroup;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.psi.PsiFile;
import net.seesharpsoft.intellij.plugins.csv.CsvLanguage;
import net.seesharpsoft.intellij.plugins.csv.CsvSeparatorHolder;
import net.seesharpsoft.intellij.plugins.csv.settings.CsvCodeStyleSettings;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

public class CsvChangeSeparatorActionGroup extends ActionGroup {

private static final AnAction[] CSV_SEPARATOR_CHANGE_ACTIONS;

static {
CSV_SEPARATOR_CHANGE_ACTIONS = new AnAction[CsvCodeStyleSettings.SUPPORTED_SEPARATORS.length + 1];
for (int i = 0; i < CSV_SEPARATOR_CHANGE_ACTIONS.length - 1; ++i) {
CSV_SEPARATOR_CHANGE_ACTIONS[i] = new CsvChangeSeparatorAction(CsvCodeStyleSettings.SUPPORTED_SEPARATORS[i], CsvCodeStyleSettings.SUPPORTED_SEPARATORS_DISPLAY[i]);
}
CSV_SEPARATOR_CHANGE_ACTIONS[CSV_SEPARATOR_CHANGE_ACTIONS.length - 1] = new CsvDefaultSeparatorAction();
}

@Override
public void update(AnActionEvent anActionEvent) {
PsiFile psiFile = anActionEvent.getData(CommonDataKeys.PSI_FILE);
Language language = psiFile == null ? null : psiFile.getLanguage();
anActionEvent.getPresentation().setEnabledAndVisible(psiFile != null && language != null &&
language.isKindOf(CsvLanguage.INSTANCE) && !(language instanceof CsvSeparatorHolder)
);

if (psiFile != null) {
anActionEvent.getPresentation()
.setText(String.format("CSV Value Separator: %s", CsvCodeStyleSettings.getSeparatorDisplayText(CsvCodeStyleSettings.getCurrentSeparator(psiFile))));
}
}

@NotNull
@Override
public AnAction[] getChildren(@Nullable AnActionEvent anActionEvent) {
return CSV_SEPARATOR_CHANGE_ACTIONS;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package net.seesharpsoft.intellij.plugins.csv.actions;

import com.intellij.lang.Language;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.ToggleAction;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.psi.PsiFile;
import com.intellij.util.FileContentUtil;
import net.seesharpsoft.intellij.plugins.csv.CsvLanguage;
import net.seesharpsoft.intellij.plugins.csv.CsvSeparatorHolder;
import net.seesharpsoft.intellij.plugins.csv.components.CsvFileAttributes;
import org.jetbrains.annotations.NotNull;

public class CsvDefaultSeparatorAction extends ToggleAction {
CsvDefaultSeparatorAction() {
super("Project Default");
}

@Override
public boolean isSelected(@NotNull AnActionEvent anActionEvent) {
PsiFile psiFile = anActionEvent.getData(CommonDataKeys.PSI_FILE);
if (psiFile == null) {
return false;
}
CsvFileAttributes csvFileAttributes = ServiceManager.getService(psiFile.getProject(), CsvFileAttributes.class);
return csvFileAttributes.getFileSeparator(psiFile) == null;
}

@Override
public void setSelected(@NotNull AnActionEvent anActionEvent, boolean b) {
PsiFile psiFile = anActionEvent.getData(CommonDataKeys.PSI_FILE);
if (psiFile == null) {
return;
}
Language language = psiFile.getLanguage();
if (!language.isKindOf(CsvLanguage.INSTANCE) || language instanceof CsvSeparatorHolder) {
return;
}
CsvFileAttributes csvFileAttributes = ServiceManager.getService(psiFile.getProject(), CsvFileAttributes.class);
csvFileAttributes.removeFileSeparator(psiFile);
FileContentUtil.reparseFiles(psiFile.getVirtualFile());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package net.seesharpsoft.intellij.plugins.csv.components;

import com.intellij.openapi.components.PersistentStateComponent;
import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage;
import com.intellij.psi.PsiFile;
import com.intellij.util.xmlb.XmlSerializerUtil;
import net.seesharpsoft.intellij.plugins.csv.CsvStorageHelper;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.HashMap;
import java.util.Map;

@State(
name = "CsvFileAttributes",
storages = {@Storage(CsvStorageHelper.CSV_STATE_STORAGE_FILE)}
)
@SuppressWarnings("all")
public class CsvFileAttributes implements PersistentStateComponent<CsvFileAttributes> {

public Map<String, Attribute> attributeMap = new HashMap<>();

static class Attribute {
public String separator;
}

@Nullable
@Override
public CsvFileAttributes getState() {
return this;
}

@Override
public void loadState(@NotNull CsvFileAttributes state) {
XmlSerializerUtil.copyBean(state, this);
}

protected String generateMapKey(@NotNull PsiFile psiFile) {
return CsvStorageHelper.getRelativeFileUrl(psiFile);
}

public void setFileSeparator(@NotNull PsiFile psiFile, @NotNull String separator) {
Attribute state = attributeMap.get(generateMapKey(psiFile));
if (state == null) {
state = new Attribute();
attributeMap.put(generateMapKey(psiFile), state);
}
state.separator = separator;
}

public void removeFileSeparator(@NotNull PsiFile psiFile) {
attributeMap.remove(generateMapKey(psiFile));
}

public String getFileSeparator(@NotNull PsiFile psiFile) {
Attribute state = attributeMap.get(generateMapKey(psiFile));
if (state != null) {
return state.separator;
}
return null;
}
}
Loading