IDEA-175757 Support File Type mapping based on shebang

Add ability to detect file type based on hasbang (#! string) inside the file contents (via FileAssocTable.findAssociatedFileTypeByHashBang()).
Store/persist hashbang patterns in FileTypeManagerImpl.
Enable users to configure hashbang associations via Settings|File types UI.
Allow plugins to configure hashbangs via <fileType hashBangs=""/> in plugin.xml
Replace some no more needed HashBangFileTypeDetectors for standard langs with xml configs

GitOrigin-RevId: 14335912b90f2d4f665d2a71eddeebf5cfc91f30
This commit is contained in:
Alexey Kudravtsev
2020-03-19 19:23:01 +00:00
committed by intellij-monorepo-bot
parent 3127717d23
commit ae21cc79ff
18 changed files with 503 additions and 299 deletions
+1 -2
View File
@@ -1259,7 +1259,7 @@
<completion.plainTextSymbol language="JAVA" implementationClass="com.intellij.codeInsight.completion.JvmPlainTextSymbolCompletionContributor"/>
<lookup.actionProvider implementation="com.intellij.codeInsight.completion.ExcludeFromCompletionLookupActionProvider" id="excludeFromCompletion" order="last"/>
<lookup.actionProvider implementation="com.intellij.codeInsight.completion.ImportStaticLookupActionProvider" id="importStatic"/>
<fileType extensions="java" name="JAVA" language="JAVA" fieldName="INSTANCE" implementationClass="com.intellij.ide.highlighter.JavaFileType"/>
<fileType extensions="java" hashBangs="java" name="JAVA" language="JAVA" fieldName="INSTANCE" implementationClass="com.intellij.ide.highlighter.JavaFileType"/>
<fileType extensions="class" name="CLASS" fieldName="INSTANCE" implementationClass="com.intellij.ide.highlighter.JavaClassFileType"/>
<fileType extensions="snippet" name="JSHELL" language="JShellLanguage" fieldName="INSTANCE" implementationClass="com.intellij.ide.highlighter.JShellFileType"/>
<customPropertyScopeProvider implementation="com.intellij.psi.impl.search.SimpleAccessorScopeProvider"/>
@@ -1990,7 +1990,6 @@
</intentionAction>
<externalAnnotationsArtifactsResolver implementation="com.intellij.jarRepository.ExternalAnnotationsRepositoryResolver"/>
<errorQuickFixProvider implementation="com.intellij.codeInsight.daemon.impl.analysis.JavaErrorQuickFixProvider"/>
<fileTypeDetector implementation="com.intellij.openapi.fileTypes.impl.JavaFileTypeDetector"/>
<searchEverywhereResultsEqualityProvider implementation="com.intellij.ide.JavaClassAndFileEqualityProvider"/>
@@ -1,10 +0,0 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.openapi.fileTypes.impl;
import com.intellij.ide.highlighter.JavaFileType;
public class JavaFileTypeDetector extends HashBangFileTypeDetector {
public JavaFileTypeDetector() {
super(JavaFileType.INSTANCE, "java");
}
}
@@ -5,6 +5,7 @@ import com.intellij.openapi.fileTypes.ExactFileNameMatcher;
import com.intellij.openapi.fileTypes.ExtensionFileNameMatcher;
import com.intellij.openapi.fileTypes.FileNameMatcher;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.io.FileUtilRt;
import com.intellij.util.ArrayUtilRt;
import com.intellij.util.text.CharSequenceHashingStrategy;
@@ -14,16 +15,19 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.stream.Collectors;
public class FileTypeAssocTable<T> {
private final Map<CharSequence, T> myExtensionMappings;
private final Map<CharSequence, T> myExactFileNameMappings;
private final Map<CharSequence, T> myExactFileNameAnyCaseMappings;
private final List<Pair<FileNameMatcher, T>> myMatchingMappings;
private final Map<String, T> myHashBangMap;
private FileTypeAssocTable(@NotNull Map<? extends CharSequence, ? extends T> extensionMappings,
@NotNull Map<? extends CharSequence, ? extends T> exactFileNameMappings,
@NotNull Map<? extends CharSequence, T> exactFileNameAnyCaseMappings,
@NotNull Map<? extends CharSequence, ? extends T> exactFileNameAnyCaseMappings,
@NotNull Map<String, ? extends T> hashBangMap,
@NotNull List<? extends Pair<FileNameMatcher, T>> matchingMappings) {
myExtensionMappings = new THashMap<>(Math.max(10, extensionMappings.size()), 0.5f, CharSequenceHashingStrategy.CASE_INSENSITIVE);
myExtensionMappings.putAll(extensionMappings);
@@ -31,11 +35,13 @@ public class FileTypeAssocTable<T> {
myExactFileNameMappings.putAll(exactFileNameMappings);
myExactFileNameAnyCaseMappings = new THashMap<>(Math.max(10, exactFileNameAnyCaseMappings.size()), 0.5f, CharSequenceHashingStrategy.CASE_INSENSITIVE);
myExactFileNameAnyCaseMappings.putAll(exactFileNameAnyCaseMappings);
myHashBangMap = new THashMap<>(Math.max(10, hashBangMap.size()), 0.5f);
myHashBangMap.putAll(hashBangMap);
myMatchingMappings = new ArrayList<>(matchingMappings);
}
public FileTypeAssocTable() {
this(Collections.emptyMap(), Collections.emptyMap(), Collections.emptyMap(), Collections.emptyList());
this(Collections.emptyMap(), Collections.emptyMap(), Collections.emptyMap(), Collections.emptyMap(), Collections.emptyList());
}
boolean isAssociatedWith(@NotNull T type, @NotNull FileNameMatcher matcher) {
@@ -65,14 +71,21 @@ public class FileTypeAssocTable<T> {
}
}
boolean removeAssociation(@NotNull FileNameMatcher matcher, @NotNull T type) {
void addHashBangPattern(@NotNull String hashBang, @NotNull T type) {
myHashBangMap.put(hashBang, type);
}
void removeHashBangPattern(@NotNull String hashBang, @NotNull T type) {
myHashBangMap.remove(hashBang, type);
}
void removeAssociation(@NotNull FileNameMatcher matcher, @NotNull T type) {
if (matcher instanceof ExtensionFileNameMatcher) {
String extension = ((ExtensionFileNameMatcher)matcher).getExtension();
if (myExtensionMappings.get(extension) == type) {
myExtensionMappings.remove(extension);
return true;
return;
}
return false;
return;
}
if (matcher instanceof ExactFileNameMatcher) {
@@ -80,27 +93,26 @@ public class FileTypeAssocTable<T> {
String fileName = exactFileNameMatcher.getFileName();
final Map<CharSequence, T> mapToUse = exactFileNameMatcher.isIgnoreCase() ? myExactFileNameAnyCaseMappings : myExactFileNameMappings;
if(mapToUse.get(fileName) == type) {
if (mapToUse.get(fileName) == type) {
mapToUse.remove(fileName);
return true;
}
return false;
return;
}
return myMatchingMappings.removeIf(assoc -> matcher.equals(assoc.getFirst()));
myMatchingMappings.removeIf(assoc -> matcher.equals(assoc.getFirst()));
}
boolean removeAllAssociations(@NotNull T type) {
boolean changed = removeAssociationsFromMap(myExtensionMappings, type, false);
void removeAllAssociations(@NotNull T type) {
removeAssociationsFromMap(myExtensionMappings, type);
changed = removeAssociationsFromMap(myExactFileNameAnyCaseMappings, type, changed);
changed = removeAssociationsFromMap(myExactFileNameMappings, type, changed);
removeAssociationsFromMap(myExactFileNameAnyCaseMappings, type);
removeAssociationsFromMap(myExactFileNameMappings, type);
return myMatchingMappings.removeIf(assoc -> assoc.getSecond() == type);
myMatchingMappings.removeIf(assoc -> assoc.getSecond() == type);
myHashBangMap.entrySet().removeIf(e -> e.getValue().equals(type));
}
private boolean removeAssociationsFromMap(@NotNull Map<CharSequence, T> extensionMappings, @NotNull T type, boolean changed) {
return extensionMappings.entrySet().removeIf(entry -> entry.getValue() == type) || changed;
private void removeAssociationsFromMap(@NotNull Map<CharSequence, T> extensionMappings, @NotNull T type) {
extensionMappings.entrySet().removeIf(entry -> entry.getValue() == type);
}
@Nullable
@@ -117,13 +129,22 @@ public class FileTypeAssocTable<T> {
//noinspection ForLoopReplaceableByForEach
for (int i = 0; i < myMatchingMappings.size(); i++) {
final Pair<FileNameMatcher, T> mapping = myMatchingMappings.get(i);
Pair<FileNameMatcher, T> mapping = myMatchingMappings.get(i);
if (mapping.getFirst().acceptsCharSequence(fileName)) return mapping.getSecond();
}
return findByExtension(FileUtilRt.getExtension(fileName));
}
@Nullable
T findAssociatedFileTypeByHashBang(@NotNull CharSequence content) {
for (Map.Entry<String, T> entry : myHashBangMap.entrySet()) {
String hashBang = entry.getKey();
if (FileUtil.isHashBangLine(content, hashBang)) return entry.getValue();
}
return null;
}
@Nullable
T findAssociatedFileType(@NotNull FileNameMatcher matcher) {
if (matcher instanceof ExtensionFileNameMatcher) {
@@ -148,20 +169,19 @@ public class FileTypeAssocTable<T> {
return myExtensionMappings.get(extension);
}
@Deprecated
String @NotNull [] getAssociatedExtensions(@NotNull T type) {
List<String> exts = new ArrayList<>();
List<String> extensions = new ArrayList<>();
for (Map.Entry<CharSequence, T> entry : myExtensionMappings.entrySet()) {
if (entry.getValue() == type) {
exts.add(entry.getKey().toString());
extensions.add(entry.getKey().toString());
}
}
return ArrayUtilRt.toStringArray(exts);
return ArrayUtilRt.toStringArray(extensions);
}
@NotNull
public FileTypeAssocTable<T> copy() {
return new FileTypeAssocTable<>(myExtensionMappings, myExactFileNameMappings, myExactFileNameAnyCaseMappings, myMatchingMappings);
return new FileTypeAssocTable<>(myExtensionMappings, myExactFileNameMappings, myExactFileNameAnyCaseMappings, myHashBangMap, myMatchingMappings);
}
@NotNull
@@ -192,9 +212,18 @@ public class FileTypeAssocTable<T> {
return result;
}
@NotNull
public List<String> getHashBangPatterns(@NotNull T type) {
return myHashBangMap.entrySet().stream()
.filter(e -> e.getValue().equals(type))
.map(e->e.getKey())
.collect(Collectors.toList());
}
boolean hasAssociationsFor(@NotNull T fileType) {
if (myExtensionMappings.containsValue(fileType) ||
myExactFileNameMappings.containsValue(fileType) ||
myHashBangMap.containsValue(fileType) ||
myExactFileNameAnyCaseMappings.containsValue(fileType)) {
return true;
}
@@ -228,10 +257,11 @@ public class FileTypeAssocTable<T> {
return false;
}
FileTypeAssocTable<?> that = (FileTypeAssocTable)o;
FileTypeAssocTable<?> that = (FileTypeAssocTable<?>)o;
return myExtensionMappings.equals(that.myExtensionMappings) &&
myMatchingMappings.equals(that.myMatchingMappings) &&
myExactFileNameMappings.equals(that.myExactFileNameMappings) &&
myHashBangMap.equals(that.myHashBangMap) &&
myExactFileNameAnyCaseMappings.equals(that.myExactFileNameAnyCaseMappings);
}
@@ -239,8 +269,14 @@ public class FileTypeAssocTable<T> {
public int hashCode() {
int result = myExtensionMappings.hashCode();
result = 31 * result + myMatchingMappings.hashCode();
result = 31 * result + myHashBangMap.hashCode();
result = 31 * result + myExactFileNameMappings.hashCode();
result = 31 * result + myExactFileNameAnyCaseMappings.hashCode();
return result;
}
@NotNull
Map<String, T> getAllHashBangPatterns() {
return Collections.unmodifiableMap(new THashMap<>(myHashBangMap));
}
}
@@ -8,14 +8,10 @@ import com.intellij.lang.Language;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.fileTypes.*;
import com.intellij.openapi.options.Configurable;
import com.intellij.openapi.options.ConfigurationException;
import com.intellij.openapi.options.SearchableConfigurable;
import com.intellij.openapi.options.SettingsEditor;
import com.intellij.openapi.ui.DialogBuilder;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.wm.IdeFocusManager;
@@ -44,6 +40,7 @@ public class FileTypeConfigurable implements SearchableConfigurable, Configurabl
private RecognizedFileTypes myRecognizedFileType;
private PatternsPanel myPatterns;
private HashBangPanel myHashBangs;
private FileTypePanel myFileTypePanel;
private Set<FileType> myTempFileTypes;
private final FileTypeManagerImpl myManager;
@@ -62,14 +59,15 @@ public class FileTypeConfigurable implements SearchableConfigurable, Configurabl
@Override
public JComponent createComponent() {
myFileTypePanel = new FileTypePanel().init();
myRecognizedFileType = myFileTypePanel.myRecognizedFileType;
myPatterns = myFileTypePanel.myPatterns;
myRecognizedFileType.attachActions(this);
myRecognizedFileType.myFileTypesList.addListSelectionListener(e -> updateExtensionList());
myPatterns.attachActions(this);
myFileTypePanel = new FileTypePanel();
myFileTypePanel.myIgnorePanel.setBorder(
IdeBorderFactory.createTitledBorder(IdeBundle.message("editbox.ignore.files.and.folders"), false, TITLE_INSETS).setShowLine(false));
myRecognizedFileType = new RecognizedFileTypes(myFileTypePanel.myRecognizedFileTypesPanel);
myPatterns = new PatternsPanel(myFileTypePanel.myPatternsPanel);
myHashBangs = new HashBangPanel(myFileTypePanel.myHashBangPanel);
myRecognizedFileType.myFileTypesList.addListSelectionListener(__ -> updateExtensionList());
myFileTypePanel.myIgnoreFilesField.setColumns(30);
return myFileTypePanel.getComponent();
return myFileTypePanel.myWholePanel;
}
private void updateFileTypeList() {
@@ -125,10 +123,15 @@ public class FileTypeConfigurable implements SearchableConfigurable, Configurabl
@Override
public void disposeUIResources() {
if (myFileTypePanel != null) myFileTypePanel.dispose();
if (myFileTypePanel != null) {
myRecognizedFileType.setFileTypes(FileType.EMPTY_ARRAY);
myPatterns.clearList();
myHashBangs.clearList();
}
myFileTypePanel = null;
myRecognizedFileType = null;
myPatterns = null;
myHashBangs = null;
}
private static class ExtensionRenderer extends DefaultListCellRenderer {
@@ -155,12 +158,8 @@ public class FileTypeConfigurable implements SearchableConfigurable, Configurabl
extensions.add(assoc.getPresentableString());
}
myPatterns.clearList();
Collections.sort(extensions);
for (String extension : extensions) {
myPatterns.addPattern(extension);
}
myPatterns.ensureSelectionExists();
myPatterns.refill(extensions);
myHashBangs.refill(myTempPatternsTable.getHashBangPatterns(type));
}
private void editFileType() {
@@ -224,7 +223,7 @@ public class FileTypeConfigurable implements SearchableConfigurable, Configurabl
Language oldLanguage = item == null ? null : myTempTemplateDataLanguages.findAssociatedFileType(item);
FileTypePatternDialog dialog = new FileTypePatternDialog(item, type, oldLanguage);
DialogBuilder builder = new DialogBuilder(myPatterns);
DialogBuilder builder = new DialogBuilder(myPatterns.myList);
builder.setPreferredFocusComponent(dialog.getPatternField());
builder.setCenterPanel(dialog.getMainPanel());
builder.setTitle(title);
@@ -237,16 +236,16 @@ public class FileTypeConfigurable implements SearchableConfigurable, Configurabl
FileType registeredFileType = findExistingFileType(matcher);
if (registeredFileType != null && registeredFileType != type) {
if (registeredFileType.isReadOnly()) {
Messages.showMessageDialog(myPatterns.myPatternsList,
Messages.showMessageDialog(myPatterns.myList,
FileTypesBundle.message("filetype.edit.add.pattern.exists.error", registeredFileType.getDescription()),
title, Messages.getErrorIcon());
return;
}
int ret = Messages.showOkCancelDialog(myPatterns.myPatternsList, FileTypesBundle.message("filetype.edit.add.pattern.exists.message",
registeredFileType.getDescription()),
FileTypesBundle.message("filetype.edit.add.pattern.exists.title"),
FileTypesBundle.message("filetype.edit.add.pattern.reassign.button"),
CommonBundle.getCancelButtonText(), Messages.getQuestionIcon());
int ret = Messages.showOkCancelDialog(myPatterns.myList, FileTypesBundle.message("filetype.edit.add.pattern.exists.message",
registeredFileType.getDescription()),
FileTypesBundle.message("filetype.edit.add.pattern.exists.title"),
FileTypesBundle.message("filetype.edit.add.pattern.reassign.button"),
CommonBundle.getCancelButtonText(), Messages.getQuestionIcon());
if (ret == Messages.OK) {
myTempPatternsTable.removeAssociation(matcher, registeredFileType);
if (oldLanguage != null) {
@@ -272,11 +271,8 @@ public class FileTypeConfigurable implements SearchableConfigurable, Configurabl
}
updateExtensionList();
int index = myPatterns.getListModel().indexOf(matcher.getPresentableString());
if (index >= 0) {
ScrollingUtil.selectItem(myPatterns.myPatternsList, index);
}
IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(() -> IdeFocusManager.getGlobalInstance().requestFocus(myPatterns.myPatternsList, true));
myPatterns.select(pattern);
IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(() -> IdeFocusManager.getGlobalInstance().requestFocus(myPatterns.myList, true));
}
}
@@ -286,10 +282,10 @@ public class FileTypeConfigurable implements SearchableConfigurable, Configurabl
@Nullable
private FileType findExistingFileType(@NotNull FileNameMatcher matcher) {
FileType fileTypeByExtension = myTempPatternsTable.findAssociatedFileType(matcher);
FileType type = myTempPatternsTable.findAssociatedFileType(matcher);
if (fileTypeByExtension != null && fileTypeByExtension != FileTypes.UNKNOWN) {
return fileTypeByExtension;
if (type != null && type != FileTypes.UNKNOWN) {
return type;
}
FileType registeredFileType = FileTypeManager.getInstance().getFileTypeByExtension(matcher.getPresentableString());
if (registeredFileType != FileTypes.UNKNOWN && registeredFileType.isReadOnly()) {
@@ -306,7 +302,16 @@ public class FileTypeConfigurable implements SearchableConfigurable, Configurabl
FileNameMatcher matcher = FileTypeManager.parseFromString(extension);
myTempPatternsTable.removeAssociation(matcher, type);
IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(() -> IdeFocusManager.getGlobalInstance().requestFocus(myPatterns.myPatternsList, true));
IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(() -> IdeFocusManager.getGlobalInstance().requestFocus(myPatterns.myList, true));
}
private void removeHashBang() {
FileType type = myRecognizedFileType.getSelectedFileType();
if (type == null) return;
String extension = myHashBangs.removeSelected();
if (extension == null) return;
myTempPatternsTable.removeHashBangPattern(extension, type);
IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(() -> IdeFocusManager.getGlobalInstance().requestFocus(myHashBangs.myList, true));
}
@NotNull
@@ -315,13 +320,10 @@ public class FileTypeConfigurable implements SearchableConfigurable, Configurabl
return "preferences.fileTypes";
}
public static class RecognizedFileTypes extends JPanel {
class RecognizedFileTypes {
private final JList<FileType> myFileTypesList = new JBList<>(new DefaultListModel<>());
private final MySpeedSearch mySpeedSearch;
private FileTypeConfigurable myController;
public RecognizedFileTypes() {
super(new BorderLayout());
RecognizedFileTypes(@NotNull JPanel panel) {
panel.setLayout(new BorderLayout());
myFileTypesList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
myFileTypesList.setCellRenderer(new FileTypeRenderer(() -> {
@@ -335,15 +337,15 @@ public class FileTypeConfigurable implements SearchableConfigurable, Configurabl
new DoubleClickListener() {
@Override
protected boolean onDoubleClick(@NotNull MouseEvent e) {
myController.editFileType();
editFileType();
return true;
}
}.installOn(myFileTypesList);
ToolbarDecorator toolbarDecorator = ToolbarDecorator.createDecorator(myFileTypesList)
.setAddAction(button -> myController.addFileType())
.setRemoveAction(button -> myController.removeFileType())
.setEditAction(button -> myController.editFileType())
.setAddAction(__ -> addFileType())
.setRemoveAction(__ -> removeFileType())
.setEditAction(__ -> editFileType())
.setEditActionUpdater(e -> {
FileType fileType = getSelectedFileType();
return canBeModified(fileType);
@@ -351,15 +353,14 @@ public class FileTypeConfigurable implements SearchableConfigurable, Configurabl
.setRemoveActionUpdater(e -> canBeModified(getSelectedFileType()))
.disableUpDownActions();
add(toolbarDecorator.createPanel(), BorderLayout.CENTER);
setBorder(IdeBorderFactory.createTitledBorder(FileTypesBundle.message("filetype.recognized.group"), false, TITLE_INSETS).setShowLine(false));
panel.add(toolbarDecorator.createPanel(), BorderLayout.CENTER);
panel.setBorder(IdeBorderFactory.createTitledBorder(FileTypesBundle.message("filetype.recognized.group"), false, TITLE_INSETS).setShowLine(false));
mySpeedSearch = new MySpeedSearch(myFileTypesList);
new MySpeedSearch(myFileTypesList);
}
private static class MySpeedSearch extends SpeedSearchBase<JList<FileType>> {
private class MySpeedSearch extends SpeedSearchBase<JList<FileType>> {
private final List<Condition<Pair<Object, String>>> myOrderedConverters;
private FileTypeConfigurable myController;
private Object myCurrentType;
private String myExtension;
@@ -408,43 +409,34 @@ public class FileTypeConfigurable implements SearchableConfigurable, Configurabl
protected void selectElement(Object element, String selectedText) {
if (element != null) {
ScrollingUtil.selectItem(myComponent, (FileType)element);
if (myCurrentType != null && myCurrentType.equals(element) && myController != null) {
myController.myPatterns.select(myExtension);
if (element.equals(myCurrentType)) {
myPatterns.select(myExtension);
}
}
}
@Override
protected void onSearchFieldUpdated(String s) {
if (myController == null || myController.myTempPatternsTable == null) return;
if (myTempPatternsTable == null) return;
int index = s.lastIndexOf('.');
if (index < 0) {
s = "." + s;
}
myCurrentType = myController.myTempPatternsTable.findAssociatedFileType(s);
myCurrentType = myTempPatternsTable.findAssociatedFileType(s);
if (myCurrentType != null) {
myExtension = s;
} else {
}
else {
myExtension = null;
}
}
}
void attachActions(@NotNull FileTypeConfigurable controller) {
myController = controller;
mySpeedSearch.myController = controller;
}
FileType getSelectedFileType() {
return myFileTypesList.getSelectedValue();
}
@NotNull
public JComponent getComponent() {
return this;
}
public void setFileTypes(FileType @NotNull [] types) {
void setFileTypes(FileType @NotNull [] types) {
DefaultListModel<FileType> listModel = (DefaultListModel<FileType>)myFileTypesList.getModel();
listModel.clear();
for (FileType type : types) {
@@ -455,60 +447,47 @@ public class FileTypeConfigurable implements SearchableConfigurable, Configurabl
ScrollingUtil.ensureSelectionExists(myFileTypesList);
}
void selectFileType(FileType fileType) {
void selectFileType(@NotNull FileType fileType) {
myFileTypesList.setSelectedValue(fileType, true);
IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(() -> IdeFocusManager.getGlobalInstance().requestFocus(myFileTypesList, true));
}
}
static class PatternsPanel extends JPanel {
private final JBList<String> myPatternsList = new JBList<>(new DefaultListModel<>());
private FileTypeConfigurable myController;
class PatternsPanel {
private final JBList<String> myList = new JBList<>(new DefaultListModel<>());
PatternsPanel() {
super(new BorderLayout());
myPatternsList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
myPatternsList.setCellRenderer(new ExtensionRenderer());
myPatternsList.getEmptyText().setText(FileTypesBundle.message("filetype.settings.no.patterns"));
PatternsPanel(@NotNull JPanel panel) {
panel.setLayout(new BorderLayout());
myList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
myList.setCellRenderer(new ExtensionRenderer());
myList.getEmptyText().setText(FileTypesBundle.message("filetype.settings.no.patterns"));
ToolbarDecorator decorator = ToolbarDecorator.createDecorator(myPatternsList)
.setAddAction(__ -> myController.addPattern())
.setEditAction(__ -> myController.editPattern())
.setRemoveAction(__ -> myController.removePattern())
ToolbarDecorator decorator = ToolbarDecorator.createDecorator(myList)
.setAddAction(__ -> addPattern())
.setEditAction(__ -> editPattern())
.setRemoveAction(__ -> removePattern())
.disableUpDownActions();
add(decorator.createPanel(), BorderLayout.CENTER);
panel.add(decorator.createPanel(), BorderLayout.CENTER);
setBorder(IdeBorderFactory.createTitledBorder(FileTypesBundle.message("filetype.registered.patterns.group"), false, TITLE_INSETS).setShowLine(false));
}
void attachActions(@NotNull FileTypeConfigurable controller) {
myController = controller;
panel.setBorder(IdeBorderFactory.createTitledBorder(FileTypesBundle.message("filetype.registered.patterns.group"), false, TITLE_INSETS).setShowLine(false));
}
void clearList() {
getListModel().clear();
myPatternsList.clearSelection();
myList.clearSelection();
}
@NotNull
private DefaultListModel<String> getListModel() {
return (DefaultListModel<String>)myPatternsList.getModel();
}
void addPattern(@NotNull String pattern) {
getListModel().addElement(pattern);
}
void ensureSelectionExists() {
ScrollingUtil.ensureSelectionExists(myPatternsList);
return (DefaultListModel<String>)myList.getModel();
}
void select(@NotNull String pattern) {
for (int i = 0; i < myPatternsList.getItemsCount(); i++) {
String at = myPatternsList.getModel().getElementAt(i);
for (int i = 0; i < myList.getItemsCount(); i++) {
String at = myList.getModel().getElementAt(i);
FileNameMatcher matcher = FileTypeManager.parseFromString(at);
if (matcher.acceptsCharSequence(pattern)) {
ScrollingUtil.selectItem(myPatternsList, i);
ScrollingUtil.selectItem(myList, i);
return;
}
}
@@ -517,79 +496,184 @@ public class FileTypeConfigurable implements SearchableConfigurable, Configurabl
String removeSelected() {
String selectedValue = getSelectedItem();
if (selectedValue == null) return null;
ListUtil.removeSelectedItems(myPatternsList);
ListUtil.removeSelectedItems(myList);
return selectedValue;
}
String getSelectedItem() {
return myPatternsList.getSelectedValue();
}
}
private static class FileTypePanel {
private JPanel myWholePanel;
private RecognizedFileTypes myRecognizedFileType;
private PatternsPanel myPatterns;
private JTextField myIgnoreFilesField;
private JPanel myIgnorePanel;
JComponent getComponent() {
return myWholePanel;
return myList.getSelectedValue();
}
void dispose() {
myRecognizedFileType.setFileTypes(FileType.EMPTY_ARRAY);
myPatterns.clearList();
}
private FileTypePanel init() {
myIgnorePanel.setBorder(
IdeBorderFactory.createTitledBorder(IdeBundle.message("editbox.ignore.files.and.folders"), false, TITLE_INSETS).setShowLine(false));
return this;
}
}
private static class TypeEditor<T extends UserFileType<T>> extends DialogWrapper {
private final T myFileType;
private final SettingsEditor<T> myEditor;
TypeEditor(Component parent, T fileType, String title) {
super(parent, false);
myFileType = fileType;
myEditor = fileType.getEditor();
setTitle(title);
init();
Disposer.register(myDisposable, myEditor);
}
@Override
protected void init() {
super.init();
myEditor.resetFrom(myFileType);
}
@Override
protected JComponent createCenterPanel() {
return myEditor.getComponent();
}
@Override
protected void doOKAction() {
try {
myEditor.applyTo(myFileType);
private void refill(@NotNull List<String> extensions) {
clearList();
Collections.sort(extensions);
for (String extension : extensions) {
getListModel().addElement(extension);
}
catch (ConfigurationException e) {
Messages.showErrorDialog(getContentPane(), e.getMessage(), e.getTitle());
ScrollingUtil.ensureSelectionExists(myList);
}
}
class HashBangPanel {
private final JBList<String> myList = new JBList<>(new DefaultListModel<>());
HashBangPanel(@NotNull JPanel panel) {
panel.setLayout(new BorderLayout());
myList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
myList.setCellRenderer(new ExtensionRenderer(){
@Override
public @NotNull Component getListCellRendererComponent(@NotNull JList list,
Object value,
int index,
boolean isSelected,
boolean cellHasFocus) {
Component component = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
setText(" #!*"+value+"*");
return component;
}
});
myList.getEmptyText().setText(FileTypesBundle.message("filetype.settings.no.patterns"));
ToolbarDecorator decorator = ToolbarDecorator.createDecorator(myList)
.setAddAction(__ -> editHashBang(null))
.setAddActionName("Add HashBang Pattern")
.setEditAction(__ -> editHashBang())
.setRemoveAction(__ -> removeHashBang())
.disableUpDownActions();
panel.add(decorator.createPanel(), BorderLayout.CENTER);
panel.setBorder(IdeBorderFactory.createTitledBorder(FileTypesBundle.message("filetype.hashbang.group"), false, TITLE_INSETS).setShowLine(false));
}
void clearList() {
getListModel().clear();
myList.clearSelection();
}
@NotNull
private DefaultListModel<String> getListModel() {
return (DefaultListModel<String>)myList.getModel();
}
void select(@NotNull String pattern) {
ScrollingUtil.selectItem(myList, pattern);
}
String removeSelected() {
String selectedValue = getSelectedItem();
if (selectedValue == null) return null;
ListUtil.removeSelectedItems(myList);
return selectedValue;
}
String getSelectedItem() {
return myList.getSelectedValue();
}
private void refill(@NotNull List<String> values) {
clearList();
Collections.sort(values);
for (String extension : values) {
getListModel().addElement(extension);
}
ScrollingUtil.ensureSelectionExists(myList);
}
}
private void editHashBang() {
String item = myHashBangs.getSelectedItem();
if (item == null) return;
editHashBang(item);
}
private void editHashBang(@Nullable("null means new") String oldHashBang) {
FileType type = myRecognizedFileType.getSelectedFileType();
if (type == null) return;
String title = FileTypesBundle.message("filetype.edit.hashbang.title");
Language oldLanguage = oldHashBang == null ? null : myTempTemplateDataLanguages.findAssociatedFileType(oldHashBang);
String hashbang = Messages.showInputDialog(myHashBangs.myList, FileTypesBundle.message("filetype.edit.hashbang.prompt"), title, null, oldHashBang, null);
if (StringUtil.isEmpty(hashbang)) {
return; //canceled or empty
}
HashBangConflict conflict = checkHashBangConflict(hashbang);
if (conflict != null) {
FileType existingFileType = conflict.fileType;
if (existingFileType == type) return; // ignore duplicate
if (!conflict.writeable) {
String message = conflict.exact ? FileTypesBundle.message("filetype.edit.hashbang.exists.exact.error", existingFileType.getDescription())
: FileTypesBundle.message("filetype.edit.hashbang.exists.similar.error", existingFileType.getDescription(), conflict.existingHashBang);
Messages.showMessageDialog(myHashBangs.myList, message, title, Messages.getErrorIcon());
return;
}
super.doOKAction();
String message = conflict.exact ? FileTypesBundle.message("filetype.edit.hashbang.exists.exact.message", existingFileType.getDescription())
: FileTypesBundle.message("filetype.edit.hashbang.exists.similar.message", existingFileType.getDescription(), conflict.existingHashBang);
int ret = Messages.showOkCancelDialog(myHashBangs.myList, message,
FileTypesBundle.message("filetype.edit.hashbang.exists.title"),
FileTypesBundle.message("filetype.edit.hashbang.reassign.button"),
CommonBundle.getCancelButtonText(), Messages.getQuestionIcon());
if (ret != Messages.OK) {
return;
}
myTempPatternsTable.removeHashBangPattern(hashbang, existingFileType);
if (oldLanguage != null) {
myTempTemplateDataLanguages.removeHashBangPattern(hashbang, oldLanguage);
}
myTempPatternsTable.removeHashBangPattern(conflict.existingHashBang, conflict.fileType);
}
if (oldHashBang != null) {
myTempPatternsTable.removeHashBangPattern(oldHashBang, type);
if (oldLanguage != null) {
myTempTemplateDataLanguages.removeHashBangPattern(oldHashBang, oldLanguage);
}
}
myTempPatternsTable.addHashBangPattern(hashbang, type);
@Override
protected String getHelpId() {
//noinspection SpellCheckingInspection
return "reference.dialogs.newfiletype";
updateExtensionList();
myHashBangs.select(hashbang);
IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(() -> IdeFocusManager.getGlobalInstance().requestFocus(myPatterns.myList, true));
}
// describes conflict between two hashbang patterns when user tried to create new/edit existing hashbang
private static class HashBangConflict {
FileType fileType; // conflicting file type
boolean exact; // true: conflict with the file type with the exactly the same hashbang/false: similar hashbang (more selective or less selective)
boolean writeable; //file type can be changed
String existingHashBang; // the hashbang of the conflicting file type
}
private static boolean isStandardFileType(@NotNull FileType fileType) {
return FileTypeManager.getInstance().getStdFileType(fileType.getName()) == fileType;
}
// check if there is a conflict between new hasbang and exising ones
private HashBangConflict checkHashBangConflict(@NotNull String hashbang) {
HashBangConflict conflict = new HashBangConflict();
for (Map.Entry<String, FileType> entry : myTempPatternsTable.getAllHashBangPatterns().entrySet()) {
String existingHashBang = entry.getKey();
if (hashbang.contains(existingHashBang) || existingHashBang.contains(hashbang)) {
conflict.fileType = entry.getValue();
conflict.exact = existingHashBang.equals(hashbang);
conflict.writeable = !conflict.fileType.isReadOnly() && !isStandardFileType(conflict.fileType);
conflict.existingHashBang = existingHashBang;
return conflict;
}
}
List<FileTypeRegistry.FileTypeDetector> detectors = FileTypeRegistry.FileTypeDetector.EP_NAME.getExtensionList();
for (FileTypeRegistry.FileTypeDetector detector : detectors) {
if (detector instanceof HashBangFileTypeDetector) {
String existingHashBang = ((HashBangFileTypeDetector)detector).getMarker();
if (hashbang.contains(existingHashBang) || existingHashBang.contains(hashbang)) {
conflict.fileType = ((HashBangFileTypeDetector)detector).getFileType();
conflict.exact = existingHashBang.equals(hashbang);
conflict.writeable = false;
conflict.existingHashBang = existingHashBang;
return conflict;
}
}
}
return null;
}
@NotNull
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.intellij.openapi.fileTypes.impl.FileTypeConfigurable.FileTypePanel">
<grid id="975b3" binding="myWholePanel" layout-manager="GridLayoutManager" row-count="3" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.intellij.openapi.fileTypes.impl.FileTypePanel">
<grid id="975b3" binding="myWholePanel" layout-manager="GridLayoutManager" row-count="4" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<xy x="40" y="17" width="426" height="416"/>
@@ -8,22 +8,28 @@
<properties/>
<border type="none"/>
<children>
<component id="1ff7f" class="com.intellij.openapi.fileTypes.impl.FileTypeConfigurable$PatternsPanel" binding="myPatterns">
<grid id="b8997" binding="myPatternsPanel" layout-manager="GridLayoutManager" row-count="1" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="7" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
</component>
<component id="10cf2" class="com.intellij.openapi.fileTypes.impl.FileTypeConfigurable$RecognizedFileTypes" binding="myRecognizedFileType">
<border type="none"/>
<children/>
</grid>
<grid id="1671e" binding="myRecognizedFileTypesPanel" layout-manager="GridLayoutManager" row-count="1" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="7" hsize-policy="7" anchor="8" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
</component>
<border type="none"/>
<children/>
</grid>
<grid id="846ad" binding="myIgnorePanel" layout-manager="GridLayoutManager" row-count="1" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
<grid row="3" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
<clientProperties>
@@ -45,6 +51,15 @@
</component>
</children>
</grid>
<grid id="79b1b" binding="myHashBangPanel" layout-manager="GridLayoutManager" row-count="1" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
<border type="none"/>
<children/>
</grid>
</children>
</grid>
</form>
@@ -0,0 +1,13 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.openapi.fileTypes.impl;
import javax.swing.*;
class FileTypePanel {
JPanel myWholePanel;
JPanel myRecognizedFileTypesPanel;
JPanel myPatternsPanel;
JTextField myIgnoreFilesField;
JPanel myIgnorePanel;
JPanel myHashBangPanel;
}
@@ -0,0 +1,56 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.openapi.fileTypes.impl;
import com.intellij.openapi.fileTypes.UserFileType;
import com.intellij.openapi.options.ConfigurationException;
import com.intellij.openapi.options.SettingsEditor;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Disposer;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.awt.*;
class TypeEditor<T extends UserFileType<T>> extends DialogWrapper {
private final T myFileType;
private final SettingsEditor<T> myEditor;
TypeEditor(@NotNull Component parent, @NotNull T fileType, @NotNull String title) {
super(parent, false);
myFileType = fileType;
myEditor = fileType.getEditor();
setTitle(title);
init();
Disposer.register(myDisposable, myEditor);
}
@Override
protected void init() {
super.init();
myEditor.resetFrom(myFileType);
}
@Override
protected JComponent createCenterPanel() {
return myEditor.getComponent();
}
@Override
protected void doOKAction() {
try {
myEditor.applyTo(myFileType);
}
catch (ConfigurationException e) {
Messages.showErrorDialog(getContentPane(), e.getMessage(), e.getTitle());
return;
}
super.doOKAction();
}
@Override
protected String getHelpId() {
//noinspection SpellCheckingInspection
return "reference.dialogs.newfiletype";
}
}
@@ -2,7 +2,7 @@ filetype.chooser.title=Register New File Type Association
filetype.chooser.prompt=The file ''{0}'' is not associated with any file type. Please define the association:
filetype.chooser.change.prompt=The file ''{0}'' is associated with {1} file type. Please define a new association:
filetype.chooser.association=Open matching files in {0}:
filetype.registered.patterns.group=Registered Patterns:
filetype.registered.patterns.group=File Name Patterns:
filetype.settings.title=File Types
filetype.edit.existing.title=Edit File Type
filetype.edit.new.title=New File Type
@@ -17,5 +17,14 @@ filetype.settings.component=File types
filetype.settings.no.patterns=No registered file patterns
filetype.recognized.group=Recognized File Types:
filetype.chooser.file.pattern=File &pattern:
filetype.hashbang.group=HashBang Patterns:
filetype.edit.hashbang.title=Edit HashBang Pattern
filetype.edit.hashbang.exists.exact.error=This hashbang is reserved for ''{0}'' filetype and cannot be reassigned
filetype.edit.hashbang.exists.similar.error=The similar hashbang (''{1}'') is reserved for ''{0}'' filetype and cannot be reassigned
filetype.edit.hashbang.exists.exact.message=This hashbang is already registered by ''{0}'' filetype
filetype.edit.hashbang.exists.similar.message=The similar hashbang (''{1}'') is already registered by ''{0}'' filetype
filetype.edit.hashbang.reassign.button=&Reassign hashbang
filetype.edit.hashbang.exists.title=Add HashBang
filetype.edit.hashbang.prompt=HashBang substring (E.g. 'sh' to be associated with hashbang '#!/bin/sh'):
notification.file.extension.0.was.reassigned.to.1.revert=File extension {0} was reassigned to {1} <a href=''revert''>Revert</a>
notification.title.file.type.recognized=File type recognized
@@ -82,13 +82,20 @@ public final class FileTypeBean implements PluginAware {
@Attribute("language")
public String language;
/**
* Semicolon-separated list of hash bang patterns to be associated with the file type
*/
@Attribute("hashBangs")
@NonNls
public String hashBangs;
@ApiStatus.Internal
public void addMatchers(List<? extends FileNameMatcher> matchers) {
public void addMatchers(@NotNull List<? extends FileNameMatcher> matchers) {
myMatchers.addAll(matchers);
}
@ApiStatus.Internal
public List<FileNameMatcher> getMatchers() {
public @NotNull List<FileNameMatcher> getMatchers() {
return new ArrayList<>(myMatchers);
}
@@ -14,7 +14,10 @@ import com.intellij.openapi.components.PersistentStateComponent;
import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.extensions.*;
import com.intellij.openapi.extensions.ExtensionPointListener;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.extensions.PluginDescriptor;
import com.intellij.openapi.extensions.PluginId;
import com.intellij.openapi.fileEditor.impl.LoadTextUtil;
import com.intellij.openapi.fileTypes.*;
import com.intellij.openapi.fileTypes.ex.ExternalizableFileType;
@@ -364,7 +367,7 @@ public class FileTypeManagerImpl extends FileTypeManagerEx implements Persistent
});
for (StandardFileType pair : myStandardFileTypes.values()) {
registerFileTypeWithoutNotification(pair.fileType, pair.matchers, true);
registerFileTypeWithoutNotification(pair.fileType, pair.matchers, Collections.emptyList(), true);
}
try {
@@ -438,7 +441,7 @@ public class FileTypeManagerImpl extends FileTypeManagerEx implements Persistent
}
}
private static void initializeMatchers(FileTypeBean bean) {
private static void initializeMatchers(@NotNull FileTypeBean bean) {
bean.addMatchers(ContainerUtil.concat(
parse(bean.extensions),
parse(bean.fileNames, token -> new ExactFileNameMatcher(token)),
@@ -463,14 +466,14 @@ public class FileTypeManagerImpl extends FileTypeManagerEx implements Persistent
return type.fileType;
}
private FileType instantiateFileTypeBean(@NotNull FileTypeBean fileTypeBean) {
private FileType instantiateFileTypeBean(@NotNull FileTypeBean bean) {
FileType fileType;
PluginId pluginId = fileTypeBean.getPluginDescriptor().getPluginId();
PluginId pluginId = bean.getPluginDescriptor().getPluginId();
try {
@SuppressWarnings("unchecked")
Class<FileType> beanClass = (Class<FileType>)Class.forName(fileTypeBean.implementationClass, true, fileTypeBean.getPluginDescriptor().getPluginClassLoader());
if (fileTypeBean.fieldName != null) {
Field field = beanClass.getDeclaredField(fileTypeBean.fieldName);
Class<FileType> beanClass = (Class<FileType>)Class.forName(bean.implementationClass, true, bean.getPluginDescriptor().getPluginClassLoader());
if (bean.fieldName != null) {
Field field = beanClass.getDeclaredField(bean.fieldName);
field.setAccessible(true);
fileType = (FileType)field.get(null);
}
@@ -484,23 +487,24 @@ public class FileTypeManagerImpl extends FileTypeManagerEx implements Persistent
return null;
}
if (!fileType.getName().equals(fileTypeBean.name)) {
LOG.error(new PluginException("Incorrect name specified in <fileType>, should be " + fileType.getName() + ", actual " + fileTypeBean.name, pluginId));
if (!fileType.getName().equals(bean.name)) {
LOG.error(new PluginException("Incorrect name specified in <fileType>, should be " + fileType.getName() + ", actual " + bean.name, pluginId));
}
if (fileType instanceof LanguageFileType) {
final LanguageFileType languageFileType = (LanguageFileType)fileType;
String expectedLanguage = languageFileType.isSecondary() ? null : languageFileType.getLanguage().getID();
if (!Comparing.equal(fileTypeBean.language, expectedLanguage)) {
LOG.error(new PluginException("Incorrect language specified in <fileType> for " + fileType.getName() + ", should be " + expectedLanguage + ", actual " + fileTypeBean.language, pluginId));
if (!Comparing.equal(bean.language, expectedLanguage)) {
LOG.error(new PluginException("Incorrect language specified in <fileType> for " + fileType.getName() + ", should be " + expectedLanguage + ", actual " + bean.language, pluginId));
}
}
final StandardFileType standardFileType = new StandardFileType(fileType, fileTypeBean.getMatchers());
myStandardFileTypes.put(fileTypeBean.name, standardFileType);
registerFileTypeWithoutNotification(standardFileType.fileType, standardFileType.matchers, true);
final StandardFileType standardFileType = new StandardFileType(fileType, bean.getMatchers());
myStandardFileTypes.put(bean.name, standardFileType);
List<String> hashBangs = bean.hashBangs == null ? Collections.emptyList() : StringUtil.split(bean.hashBangs, ";");
registerFileTypeWithoutNotification(fileType, standardFileType.matchers, hashBangs, true);
myPendingAssociations.removeAllAssociations(fileTypeBean);
myPendingFileTypes.remove(fileTypeBean.name);
myPendingAssociations.removeAllAssociations(bean);
myPendingFileTypes.remove(bean.name);
return fileType;
}
@@ -1083,6 +1087,9 @@ public class FileTypeManagerImpl extends FileTypeManagerEx implements Persistent
}
}
if (detected == null && text != null) {
detected = myPatternsTable.findAssociatedFileTypeByHashBang(text);
}
if (detected == null) {
detected = text == null ? UnknownFileType.INSTANCE : PlainTextFileType.INSTANCE;
if (toLog()) {
@@ -1155,7 +1162,7 @@ public class FileTypeManagerImpl extends FileTypeManagerEx implements Persistent
DeprecatedMethodException.report("Use fileType extension instead.");
ApplicationManager.getApplication().runWriteAction(() -> {
fireBeforeFileTypesChanged();
registerFileTypeWithoutNotification(type, defaultAssociations, true);
registerFileTypeWithoutNotification(type, defaultAssociations, Collections.emptyList(), true);
fireFileTypesChanged(type, null);
});
}
@@ -1386,7 +1393,8 @@ public class FileTypeManagerImpl extends FileTypeManagerEx implements Persistent
private void readGlobalMappings(@NotNull Element e, boolean isAddToInit) {
for (Pair<FileNameMatcher, String> association : AbstractFileType.readAssociations(e)) {
FileType type = getFileTypeByName(association.getSecond());
String fileTypeName = association.getSecond();
FileType type = getFileTypeByName(fileTypeName);
FileNameMatcher matcher = association.getFirst();
final FileTypeBean pendingFileTypeBean = myPendingAssociations.findAssociatedFileType(matcher);
if (pendingFileTypeBean != null) {
@@ -1406,7 +1414,16 @@ public class FileTypeManagerImpl extends FileTypeManagerEx implements Persistent
}
}
else {
myUnresolvedMappings.put(matcher, association.getSecond());
myUnresolvedMappings.put(matcher, fileTypeName);
}
}
for (Map.Entry<String, FileType> entry : readHashBangs(e).entrySet()) {
String hashBang = entry.getKey();
FileType fileType = entry.getValue();
myPatternsTable.addHashBangPattern(hashBang, fileType);
if (isAddToInit) {
myInitialAssociations.addHashBangPattern(hashBang, fileType);
}
}
@@ -1419,6 +1436,19 @@ public class FileTypeManagerImpl extends FileTypeManagerEx implements Persistent
}
}
private @NotNull Map<String, FileType> readHashBangs(@NotNull Element e) {
List<Element> children = e.getChildren("hashBang");
Map<String, FileType> result = new THashMap<>(children.size());
for (Element hashBangTag : children) {
String typeName = hashBangTag.getAttributeValue("type");
String hashBangPattern = hashBangTag.getAttributeValue("value");
FileType fileType = typeName == null ? null : getFileTypeByName(typeName);
if (hashBangPattern == null || fileType == null) continue;
result.put(hashBangPattern, fileType);
}
return result;
}
private void addIgnore(@NonNls @NotNull String ignoreMask) {
myIgnoredPatterns.addIgnoreMask(ignoreMask);
}
@@ -1483,11 +1513,13 @@ public class FileTypeManagerImpl extends FileTypeManagerEx implements Persistent
myRemovedMappingTracker.save(map);
if (!myUnresolvedMappings.isEmpty()) {
FileNameMatcher[] unresolvedMappingKeys = myUnresolvedMappings.keySet().toArray(new FileNameMatcher[0]);
Arrays.sort(unresolvedMappingKeys, Comparator.comparing(FileNameMatcher::getPresentableString));
List<Map.Entry<FileNameMatcher, String>> entries = new ArrayList<>(myUnresolvedMappings.entrySet());
entries.sort(Comparator.comparing(e->e.getKey().getPresentableString()));
for (FileNameMatcher fileNameMatcher : unresolvedMappingKeys) {
Element content = AbstractFileType.writeMapping(myUnresolvedMappings.get(fileNameMatcher), fileNameMatcher, true);
for (Map.Entry<FileNameMatcher, String> entry : entries) {
FileNameMatcher fileNameMatcher = entry.getKey();
String typeName = entry.getValue();
Element content = AbstractFileType.writeMapping(typeName, fileNameMatcher, true);
if (content != null) {
map.addContent(content);
}
@@ -1517,7 +1549,17 @@ public class FileTypeManagerImpl extends FileTypeManagerEx implements Persistent
}
}
}
List<String> readOnlyHashBangs = myInitialAssociations.getHashBangPatterns(type);
List<String> hashBangPatterns = myPatternsTable.getHashBangPatterns(type);
hashBangPatterns.sort(Comparator.naturalOrder());
for (String hashBangPattern : hashBangPatterns) {
if (!readOnlyHashBangs.contains(hashBangPattern)) {
Element hashBangTag = new Element("hashBang");
hashBangTag.setAttribute("value", hashBangPattern);
hashBangTag.setAttribute("type", type.getName());
map.addContent(hashBangTag);
}
}
myRemovedMappingTracker.saveRemovedMappingsForFileType(map, type.getName(), defaultAssociations, specifyTypeName);
}
@@ -1545,7 +1587,7 @@ public class FileTypeManagerImpl extends FileTypeManagerEx implements Persistent
}
StringTokenizer tokenizer = new StringTokenizer(semicolonDelimited, FileTypeConsumer.EXTENSION_DELIMITER, false);
ArrayList<FileNameMatcher> list = new ArrayList<>(semicolonDelimited.length() / "py;".length());
List<FileNameMatcher> list = new ArrayList<>(semicolonDelimited.length() / "py;".length());
while (tokenizer.hasMoreTokens()) {
list.add(matcherFactory.fun(tokenizer.nextToken().trim()));
}
@@ -1555,7 +1597,7 @@ public class FileTypeManagerImpl extends FileTypeManagerEx implements Persistent
/**
* Registers a standard file type. Doesn't notifyListeners any change events.
*/
private void registerFileTypeWithoutNotification(@NotNull FileType fileType, @NotNull List<? extends FileNameMatcher> matchers, boolean addScheme) {
private void registerFileTypeWithoutNotification(@NotNull FileType fileType, @NotNull List<? extends FileNameMatcher> matchers, @NotNull List<String> hasBangPatterns, boolean addScheme) {
if (addScheme) {
mySchemeManager.addScheme(fileType);
}
@@ -1563,6 +1605,10 @@ public class FileTypeManagerImpl extends FileTypeManagerEx implements Persistent
myPatternsTable.addAssociation(matcher, fileType);
myInitialAssociations.addAssociation(matcher, fileType);
}
for (String hashBang : hasBangPatterns) {
myPatternsTable.addHashBangPattern(hashBang, fileType);
myInitialAssociations.addHashBangPattern(hashBang, fileType);
}
if (fileType instanceof FileTypeIdentifiableByVirtualFile) {
mySpecialFileTypes = ArrayUtil.append(mySpecialFileTypes, (FileTypeIdentifiableByVirtualFile)fileType, FileTypeIdentifiableByVirtualFile.ARRAY_FACTORY);
@@ -1611,7 +1657,7 @@ public class FileTypeManagerImpl extends FileTypeManagerEx implements Persistent
}
setFileTypeAttributes((UserFileType)type, fileTypeName, fileTypeDescr, iconPath);
registerFileTypeWithoutNotification(type, parse(extensionsStr), isDefault);
registerFileTypeWithoutNotification(type, parse(extensionsStr), Collections.emptyList(), isDefault);
if (isDefault) {
myDefaultTypes.add(type);
@@ -989,4 +989,17 @@ public class FileTypesTest extends HeavyPlatformTestCase {
return null;
}
}
public void testHashBangPatternsCanBeConfiguredDynamically() throws IOException {
VirtualFile file0 = createTempFile("xxxx", null, "#!/usr/bin/gogogo\na=b", CharsetToolkit.UTF8_CHARSET);
assertEquals(StdFileTypes.PLAIN_TEXT, file0.getFileType());
myFileTypeManager.getExtensionMap().addHashBangPattern("gogogo", StdFileTypes.PROPERTIES);
try {
VirtualFile file = createTempFile("xxxx", null, "#!/usr/bin/gogogo\na=b", CharsetToolkit.UTF8_CHARSET);
assertEquals(StdFileTypes.PROPERTIES, file.getFileType());
}
finally {
myFileTypeManager.getExtensionMap().removeHashBangPattern("gogogo", StdFileTypes.PROPERTIES);
}
}
}
@@ -1,24 +0,0 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed 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.jetbrains.plugins.groovy;
import com.intellij.openapi.fileTypes.impl.HashBangFileTypeDetector;
public class GroovyHashBangFileTypeDetector extends HashBangFileTypeDetector {
public GroovyHashBangFileTypeDetector() {
super(GroovyFileType.GROOVY_FILE_TYPE, "groovy");
}
}
+1 -3
View File
@@ -304,7 +304,7 @@
</extensions>
<extensions defaultExtensionNs="com.intellij">
<fileType name="Groovy" language="Groovy" extensions="groovy;gy"
<fileType name="Groovy" language="Groovy" extensions="groovy;gy" hashBangs="groovy"
implementationClass="org.jetbrains.plugins.groovy.GroovyFileType" fieldName="GROOVY_FILE_TYPE"/>
<fileType name="gdsl" extensions="gdsl"
implementationClass="org.jetbrains.plugins.groovy.GdslFileType" fieldName="INSTANCE"/>
@@ -329,8 +329,6 @@
<navbar implementation="org.jetbrains.plugins.groovy.navbar.GrNavBarModelExtension"/>
<fileTypeDetector implementation="org.jetbrains.plugins.groovy.GroovyHashBangFileTypeDetector"/>
<declarationRangeHandler key="org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod"
implementationClass="org.jetbrains.plugins.groovy.codeInsight.hint.GrMethodDeclarationRangeHandler"/>
<declarationRangeHandler key="org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition"
+1 -2
View File
@@ -15,7 +15,7 @@ Adds support for working with shell script files
</li></ul>]]></description>
<extensions defaultExtensionNs="com.intellij">
<applicationService serviceInterface="com.intellij.sh.ShSupport" serviceImplementation="com.intellij.sh.ShSupport$Impl"/>
<fileType language="Shell Script" extensions="sh;bash;zsh" fieldName="INSTANCE" name="Shell Script" implementationClass="com.intellij.sh.ShFileType"/>
<fileType language="Shell Script" extensions="sh;bash;zsh" hashBangs="sh" fieldName="INSTANCE" name="Shell Script" implementationClass="com.intellij.sh.ShFileType"/>
<lang.syntaxHighlighter language="Shell Script" implementationClass="com.intellij.sh.highlighter.ShSyntaxHighlighter"/>
<lang.parserDefinition language="Shell Script" implementationClass="com.intellij.sh.parser.ShParserDefinition"/>
<lang.commenter language="Shell Script" implementationClass="com.intellij.sh.ShCommenter"/>
@@ -67,7 +67,6 @@ Adds support for working with shell script files
<spellchecker.support language="Shell Script" implementationClass="com.intellij.sh.spellchecker.ShSpellcheckingStrategy"/>
<fileTypeDetector implementation="com.intellij.sh.ShFileTypeDetector" order="first"/>
<highlightErrorFilter implementation="com.intellij.sh.ShErrorFilter"/>
<daemon.highlightInfoFilter implementation="com.intellij.sh.ShErrorFilter"/>
@@ -1,10 +0,0 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.sh;
import com.intellij.openapi.fileTypes.impl.HashBangFileTypeDetector;
public class ShFileTypeDetector extends HashBangFileTypeDetector {
public ShFileTypeDetector() {
super(ShFileType.INSTANCE, "sh");
}
}
@@ -21,6 +21,7 @@
<fileType name="Python"
language="Python"
extensions="py;pyw;"
hashBangs="python"
implementationClass="com.jetbrains.python.PythonFileType"
fieldName="INSTANCE"/>
<projectService serviceInterface="com.jetbrains.python.psi.types.TypeEvalContextCache"
@@ -27,7 +27,6 @@
fieldName="INSTANCE"/>
<fileType name="Qt UI file" extensions="ui" implementationClass="com.jetbrains.pyqt.QtUIFileType" fieldName="INSTANCE"/>
<fileType name="XML" language="XML" extensions="qrc"/>
<fileTypeDetector implementation="com.jetbrains.python.PyFileTypeDetector"/>
<editorHighlighterProvider filetype="Python" implementationClass="com.jetbrains.python.PyEditorHighlighterProvider"/>
<lang.syntaxHighlighterFactory language="Python" implementationClass="com.jetbrains.python.highlighting.PySyntaxHighlighterFactory"/>
<lang.braceMatcher language="Python" implementationClass="com.jetbrains.python.PyBraceMatcher"/>
@@ -1,27 +0,0 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed 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 com.jetbrains.python;
import com.intellij.openapi.fileTypes.impl.HashBangFileTypeDetector;
/**
* @author yole
*/
public class PyFileTypeDetector extends HashBangFileTypeDetector {
public PyFileTypeDetector() {
super(PythonFileType.INSTANCE, "python");
}
}