diff --git a/java/java-impl/src/META-INF/JavaPlugin.xml b/java/java-impl/src/META-INF/JavaPlugin.xml
index 7d018f12888d..271a25b2576c 100644
--- a/java/java-impl/src/META-INF/JavaPlugin.xml
+++ b/java/java-impl/src/META-INF/JavaPlugin.xml
@@ -1259,7 +1259,7 @@
-
+
@@ -1990,7 +1990,6 @@
-
diff --git a/java/java-impl/src/com/intellij/openapi/fileTypes/impl/JavaFileTypeDetector.java b/java/java-impl/src/com/intellij/openapi/fileTypes/impl/JavaFileTypeDetector.java
deleted file mode 100644
index 015aab867557..000000000000
--- a/java/java-impl/src/com/intellij/openapi/fileTypes/impl/JavaFileTypeDetector.java
+++ /dev/null
@@ -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");
- }
-}
diff --git a/jps/model-impl/src/com/intellij/openapi/fileTypes/impl/FileTypeAssocTable.java b/jps/model-impl/src/com/intellij/openapi/fileTypes/impl/FileTypeAssocTable.java
index fb6d90130ed8..a4d8bbd41d27 100644
--- a/jps/model-impl/src/com/intellij/openapi/fileTypes/impl/FileTypeAssocTable.java
+++ b/jps/model-impl/src/com/intellij/openapi/fileTypes/impl/FileTypeAssocTable.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 {
private final Map myExtensionMappings;
private final Map myExactFileNameMappings;
private final Map myExactFileNameAnyCaseMappings;
private final List> myMatchingMappings;
+ private final Map 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 hashBangMap,
@NotNull List extends Pair> matchingMappings) {
myExtensionMappings = new THashMap<>(Math.max(10, extensionMappings.size()), 0.5f, CharSequenceHashingStrategy.CASE_INSENSITIVE);
myExtensionMappings.putAll(extensionMappings);
@@ -31,11 +35,13 @@ public class FileTypeAssocTable {
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 {
}
}
- 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 {
String fileName = exactFileNameMatcher.getFileName();
final Map 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 extensionMappings, @NotNull T type, boolean changed) {
- return extensionMappings.entrySet().removeIf(entry -> entry.getValue() == type) || changed;
+ private void removeAssociationsFromMap(@NotNull Map extensionMappings, @NotNull T type) {
+ extensionMappings.entrySet().removeIf(entry -> entry.getValue() == type);
}
@Nullable
@@ -117,13 +129,22 @@ public class FileTypeAssocTable {
//noinspection ForLoopReplaceableByForEach
for (int i = 0; i < myMatchingMappings.size(); i++) {
- final Pair mapping = myMatchingMappings.get(i);
+ Pair 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 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 {
return myExtensionMappings.get(extension);
}
- @Deprecated
String @NotNull [] getAssociatedExtensions(@NotNull T type) {
- List exts = new ArrayList<>();
+ List extensions = new ArrayList<>();
for (Map.Entry 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 copy() {
- return new FileTypeAssocTable<>(myExtensionMappings, myExactFileNameMappings, myExactFileNameAnyCaseMappings, myMatchingMappings);
+ return new FileTypeAssocTable<>(myExtensionMappings, myExactFileNameMappings, myExactFileNameAnyCaseMappings, myHashBangMap, myMatchingMappings);
}
@NotNull
@@ -192,9 +212,18 @@ public class FileTypeAssocTable {
return result;
}
+ @NotNull
+ public List 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 {
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 {
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 getAllHashBangPatterns() {
+ return Collections.unmodifiableMap(new THashMap<>(myHashBangMap));
+ }
}
diff --git a/platform/lang-impl/src/com/intellij/openapi/fileTypes/impl/FileTypeConfigurable.java b/platform/lang-impl/src/com/intellij/openapi/fileTypes/impl/FileTypeConfigurable.java
index 41424f6c38e4..cbba81744def 100644
--- a/platform/lang-impl/src/com/intellij/openapi/fileTypes/impl/FileTypeConfigurable.java
+++ b/platform/lang-impl/src/com/intellij/openapi/fileTypes/impl/FileTypeConfigurable.java
@@ -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 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 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> {
+ private class MySpeedSearch extends SpeedSearchBase> {
private final List>> 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 listModel = (DefaultListModel)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 myPatternsList = new JBList<>(new DefaultListModel<>());
- private FileTypeConfigurable myController;
+ class PatternsPanel {
+ private final JBList 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 getListModel() {
- return (DefaultListModel)myPatternsList.getModel();
- }
-
- void addPattern(@NotNull String pattern) {
- getListModel().addElement(pattern);
- }
-
- void ensureSelectionExists() {
- ScrollingUtil.ensureSelectionExists(myPatternsList);
+ return (DefaultListModel)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> extends DialogWrapper {
- private final T myFileType;
- private final SettingsEditor 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 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 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 getListModel() {
+ return (DefaultListModel)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 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 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 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
diff --git a/platform/lang-impl/src/com/intellij/openapi/fileTypes/impl/FileTypePanel.form b/platform/lang-impl/src/com/intellij/openapi/fileTypes/impl/FileTypePanel.form
index e9d30ddbd9b8..135732f929e3 100644
--- a/platform/lang-impl/src/com/intellij/openapi/fileTypes/impl/FileTypePanel.form
+++ b/platform/lang-impl/src/com/intellij/openapi/fileTypes/impl/FileTypePanel.form
@@ -1,6 +1,6 @@
-
diff --git a/platform/lang-impl/src/com/intellij/openapi/fileTypes/impl/FileTypePanel.java b/platform/lang-impl/src/com/intellij/openapi/fileTypes/impl/FileTypePanel.java
new file mode 100644
index 000000000000..07bf11953bca
--- /dev/null
+++ b/platform/lang-impl/src/com/intellij/openapi/fileTypes/impl/FileTypePanel.java
@@ -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;
+}
diff --git a/platform/lang-impl/src/com/intellij/openapi/fileTypes/impl/TypeEditor.java b/platform/lang-impl/src/com/intellij/openapi/fileTypes/impl/TypeEditor.java
new file mode 100644
index 000000000000..cb564a756527
--- /dev/null
+++ b/platform/lang-impl/src/com/intellij/openapi/fileTypes/impl/TypeEditor.java
@@ -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> extends DialogWrapper {
+ private final T myFileType;
+ private final SettingsEditor 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";
+ }
+}
diff --git a/platform/platform-impl/resources/messages/FileTypesBundle.properties b/platform/platform-impl/resources/messages/FileTypesBundle.properties
index d27dfbfe40ca..648fd0b3c96e 100644
--- a/platform/platform-impl/resources/messages/FileTypesBundle.properties
+++ b/platform/platform-impl/resources/messages/FileTypesBundle.properties
@@ -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} Revert
notification.title.file.type.recognized=File type recognized
diff --git a/platform/platform-impl/src/com/intellij/openapi/fileTypes/impl/FileTypeBean.java b/platform/platform-impl/src/com/intellij/openapi/fileTypes/impl/FileTypeBean.java
index 27e9a38de30c..bcd66740fb94 100644
--- a/platform/platform-impl/src/com/intellij/openapi/fileTypes/impl/FileTypeBean.java
+++ b/platform/platform-impl/src/com/intellij/openapi/fileTypes/impl/FileTypeBean.java
@@ -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 getMatchers() {
+ public @NotNull List getMatchers() {
return new ArrayList<>(myMatchers);
}
diff --git a/platform/platform-impl/src/com/intellij/openapi/fileTypes/impl/FileTypeManagerImpl.java b/platform/platform-impl/src/com/intellij/openapi/fileTypes/impl/FileTypeManagerImpl.java
index a9b7f25cd485..ddf7f28a3c9d 100644
--- a/platform/platform-impl/src/com/intellij/openapi/fileTypes/impl/FileTypeManagerImpl.java
+++ b/platform/platform-impl/src/com/intellij/openapi/fileTypes/impl/FileTypeManagerImpl.java
@@ -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 beanClass = (Class)Class.forName(fileTypeBean.implementationClass, true, fileTypeBean.getPluginDescriptor().getPluginClassLoader());
- if (fileTypeBean.fieldName != null) {
- Field field = beanClass.getDeclaredField(fileTypeBean.fieldName);
+ Class beanClass = (Class)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 , should be " + fileType.getName() + ", actual " + fileTypeBean.name, pluginId));
+ if (!fileType.getName().equals(bean.name)) {
+ LOG.error(new PluginException("Incorrect name specified in , 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 for " + fileType.getName() + ", should be " + expectedLanguage + ", actual " + fileTypeBean.language, pluginId));
+ if (!Comparing.equal(bean.language, expectedLanguage)) {
+ LOG.error(new PluginException("Incorrect language specified in 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 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 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 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 readHashBangs(@NotNull Element e) {
+ List children = e.getChildren("hashBang");
+ Map 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> 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 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 readOnlyHashBangs = myInitialAssociations.getHashBangPatterns(type);
+ List 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 list = new ArrayList<>(semicolonDelimited.length() / "py;".length());
+ List 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 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);
diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/fileTypes/impl/FileTypesTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/fileTypes/impl/FileTypesTest.java
index 3908c5c65912..8bf699b687f5 100644
--- a/platform/platform-tests/testSrc/com/intellij/openapi/fileTypes/impl/FileTypesTest.java
+++ b/platform/platform-tests/testSrc/com/intellij/openapi/fileTypes/impl/FileTypesTest.java
@@ -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);
+ }
+ }
}
diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/GroovyHashBangFileTypeDetector.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/GroovyHashBangFileTypeDetector.java
deleted file mode 100644
index 9237853cea69..000000000000
--- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/GroovyHashBangFileTypeDetector.java
+++ /dev/null
@@ -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");
- }
-}
diff --git a/plugins/groovy/src/META-INF/plugin.xml b/plugins/groovy/src/META-INF/plugin.xml
index 061813d0843b..159beb8ecaee 100644
--- a/plugins/groovy/src/META-INF/plugin.xml
+++ b/plugins/groovy/src/META-INF/plugin.xml
@@ -304,7 +304,7 @@
-
@@ -329,8 +329,6 @@
-
-
]]>
-
+
@@ -67,7 +67,6 @@ Adds support for working with shell script files
-
diff --git a/plugins/sh/src/com/intellij/sh/ShFileTypeDetector.java b/plugins/sh/src/com/intellij/sh/ShFileTypeDetector.java
deleted file mode 100644
index ba3b5595d35c..000000000000
--- a/plugins/sh/src/com/intellij/sh/ShFileTypeDetector.java
+++ /dev/null
@@ -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");
- }
-}
diff --git a/python/python-psi-api/resources/META-INF/PythonPsi.xml b/python/python-psi-api/resources/META-INF/PythonPsi.xml
index 2d504217a105..974d41171bf0 100644
--- a/python/python-psi-api/resources/META-INF/PythonPsi.xml
+++ b/python/python-psi-api/resources/META-INF/PythonPsi.xml
@@ -21,6 +21,7 @@
-
diff --git a/python/src/com/jetbrains/python/PyFileTypeDetector.java b/python/src/com/jetbrains/python/PyFileTypeDetector.java
deleted file mode 100644
index b7f29fe9ba64..000000000000
--- a/python/src/com/jetbrains/python/PyFileTypeDetector.java
+++ /dev/null
@@ -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");
- }
-}