blob: 0d0377bdf7fe94a78fc115843eb88c41a1733a28 [file] [log] [blame]
/*
* 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.
*/
/**
* @author Yura Cangea
*/
package com.intellij.openapi.editor.colors.impl;
import com.intellij.ide.ui.LafManager;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.components.ExportableComponent;
import com.intellij.openapi.components.NamedComponent;
import com.intellij.openapi.components.RoamingType;
import com.intellij.openapi.components.StoragePathMacros;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.editor.colors.EditorColorsListener;
import com.intellij.openapi.editor.colors.EditorColorsManager;
import com.intellij.openapi.editor.colors.EditorColorsScheme;
import com.intellij.openapi.editor.colors.TextAttributesKey;
import com.intellij.openapi.editor.colors.ex.DefaultColorSchemesManager;
import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.options.*;
import com.intellij.openapi.util.*;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.EventDispatcher;
import com.intellij.util.ui.UIUtil;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
public class EditorColorsManagerImpl extends EditorColorsManager implements NamedJDOMExternalizable, ExportableComponent, NamedComponent {
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.colors.impl.EditorColorsManagerImpl");
private final EventDispatcher<EditorColorsListener> myListeners = EventDispatcher.create(EditorColorsListener.class);
@NonNls private static final String NODE_NAME = "global_color_scheme";
@NonNls private static final String SCHEME_NODE_NAME = "scheme";
private String myGlobalSchemeName;
public boolean USE_ONLY_MONOSPACED_FONTS = true;
private final DefaultColorSchemesManager myDefaultColorSchemesManager;
private final SchemesManager<EditorColorsScheme, EditorColorsSchemeImpl> mySchemesManager;
@NonNls private static final String NAME_ATTR = "name";
private static final String FILE_SPEC = StoragePathMacros.ROOT_CONFIG + "/colors";
private static final String FILE_EXT = ".icls";
public EditorColorsManagerImpl(DefaultColorSchemesManager defaultColorSchemesManager, SchemesManagerFactory schemesManagerFactory) {
myDefaultColorSchemesManager = defaultColorSchemesManager;
mySchemesManager = schemesManagerFactory.createSchemesManager(
FILE_SPEC,
new MySchemeProcessor(), RoamingType.PER_USER);
addDefaultSchemes();
// Load default schemes from providers
if (!isUnitTestOrHeadlessMode()) {
loadSchemesFromBeans();
}
loadAllSchemes();
loadAdditionalTextAttributes();
setGlobalSchemeInner(myDefaultColorSchemesManager.getAllSchemes()[0]);
}
private static boolean isUnitTestOrHeadlessMode() {
return ApplicationManager.getApplication().isUnitTestMode() || ApplicationManager.getApplication().isHeadlessEnvironment();
}
public TextAttributes getDefaultAttributes(TextAttributesKey key) {
final boolean dark = UIUtil.isUnderDarcula() && getScheme("Darcula") != null;
// It is reasonable to fetch attributes from Default color scheme. Otherwise if we launch IDE and then
// try switch from custom colors scheme (e.g. with dark background) to default one. Editor will show
// incorrect highlighting with "traces" of color scheme which was active during IDE startup.
final EditorColorsScheme defaultColorScheme = getScheme(dark ? "Darcula" : EditorColorsScheme.DEFAULT_SCHEME_NAME);
return defaultColorScheme.getAttributes(key);
}
private void loadSchemesFromBeans() {
for (BundledColorSchemeEP schemeEP : Extensions.getExtensions(BundledColorSchemeEP.EP_NAME)) {
String fileName = schemeEP.path + ".xml";
InputStream stream = schemeEP.getLoaderForClass().getResourceAsStream(fileName);
try {
EditorColorsSchemeImpl scheme = loadSchemeFromStream(fileName, stream);
if (scheme != null) {
mySchemesManager.addNewScheme(scheme, false);
}
}
catch (final Exception e) {
LOG.error("Cannot read scheme from " + fileName + ": " + e.getLocalizedMessage(), e);
}
}
}
private void loadAdditionalTextAttributes() {
for (AdditionalTextAttributesEP attributesEP : AdditionalTextAttributesEP.EP_NAME.getExtensions()) {
final EditorColorsScheme editorColorsScheme = mySchemesManager.findSchemeByName(attributesEP.scheme);
if (editorColorsScheme == null) {
if (!isUnitTestOrHeadlessMode()) {
LOG.warn("Cannot find scheme: " + attributesEP.scheme + " from plugin: " + attributesEP.getPluginDescriptor().getPluginId());
}
continue;
}
try {
InputStream inputStream = attributesEP.getLoaderForClass().getResourceAsStream(attributesEP.file);
Document document = JDOMUtil.loadDocument(inputStream);
((AbstractColorsScheme)editorColorsScheme).readAttributes(document.getRootElement());
}
catch (Exception e1) {
LOG.error(e1);
}
}
}
private static EditorColorsSchemeImpl loadSchemeFromStream(String schemePath, InputStream inputStream)
throws IOException, JDOMException, InvalidDataException {
if (inputStream == null) {
// Error shouldn't occur during this operation
// thus we report error instead of info
LOG.error("Cannot read scheme from " + schemePath);
return null;
}
final Document document;
try {
document = JDOMUtil.loadDocument(inputStream);
}
catch (JDOMException e) {
LOG.info("Error reading scheme from " + schemePath + ": " + e.getLocalizedMessage());
throw e;
}
return loadSchemeFromDocument(document, false);
}
@NotNull
private static EditorColorsSchemeImpl loadSchemeFromDocument(final Document document,
final boolean isEditable)
throws InvalidDataException {
final Element root = document.getRootElement();
if (root == null || !SCHEME_NODE_NAME.equals(root.getName())) {
throw new InvalidDataException();
}
final EditorColorsSchemeImpl scheme = isEditable
// editable scheme
? new EditorColorsSchemeImpl(null, DefaultColorSchemesManager.getInstance())
//not editable scheme
: new ReadOnlyColorsSchemeImpl(null, DefaultColorSchemesManager.getInstance());
scheme.readExternal(root);
return scheme;
}
// -------------------------------------------------------------------------
// Schemes manipulation routines
// -------------------------------------------------------------------------
@Override
public void addColorsScheme(@NotNull EditorColorsScheme scheme) {
if (!isDefaultScheme(scheme) && scheme.getName().trim().length() > 0) {
mySchemesManager.addNewScheme(scheme, true);
}
}
@Override
public void removeAllSchemes() {
mySchemesManager.clearAllSchemes();
addDefaultSchemes();
}
private void addDefaultSchemes() {
DefaultColorsScheme[] allDefaultSchemes = myDefaultColorSchemesManager.getAllSchemes();
for (DefaultColorsScheme defaultScheme : allDefaultSchemes) {
mySchemesManager.addNewScheme(defaultScheme, true);
}
}
// -------------------------------------------------------------------------
// Getters & Setters
// -------------------------------------------------------------------------
@NotNull
@Override
public EditorColorsScheme[] getAllSchemes() {
ArrayList<EditorColorsScheme> schemes = new ArrayList<EditorColorsScheme>(mySchemesManager.getAllSchemes());
Collections.sort(schemes, new Comparator<EditorColorsScheme>() {
@Override
public int compare(EditorColorsScheme s1, EditorColorsScheme s2) {
if (isDefaultScheme(s1) && !isDefaultScheme(s2)) return -1;
if (!isDefaultScheme(s1) && isDefaultScheme(s2)) return 1;
return s1.getName().compareToIgnoreCase(s2.getName());
}
});
return schemes.toArray(new EditorColorsScheme[schemes.size()]);
}
@Override
public void setGlobalScheme(@Nullable EditorColorsScheme scheme) {
if (setGlobalSchemeInner(scheme)) {
fireChanges(scheme);
LafManager.getInstance().updateUI();
EditorFactory.getInstance().refreshAllEditors();
}
}
private boolean setGlobalSchemeInner(@Nullable EditorColorsScheme scheme) {
String newValue = scheme == null ? getDefaultScheme().getName() : scheme.getName();
EditorColorsScheme oldValue = mySchemesManager.getCurrentScheme();
mySchemesManager.setCurrentSchemeName(newValue);
return oldValue != null && !Comparing.equal(newValue, oldValue.getName());
}
@NotNull
private static DefaultColorsScheme getDefaultScheme() {
return DefaultColorSchemesManager.getInstance().getAllSchemes()[0];
}
@NotNull
@Override
public EditorColorsScheme getGlobalScheme() {
final EditorColorsScheme scheme = mySchemesManager.getCurrentScheme();
if (scheme == null) {
return getDefaultScheme();
}
return scheme;
}
@Override
public EditorColorsScheme getScheme(String schemeName) {
return mySchemesManager.findSchemeByName(schemeName);
}
private void fireChanges(EditorColorsScheme scheme) {
myListeners.getMulticaster().globalSchemeChange(scheme);
}
// -------------------------------------------------------------------------
// Routines responsible for loading & saving colors schemes.
// -------------------------------------------------------------------------
private void loadAllSchemes() {
mySchemesManager.loadSchemes();
}
private static File getColorsDir(boolean create) {
@NonNls String directoryPath = PathManager.getConfigPath() + File.separator + "colors";
File directory = new File(directoryPath);
if (!directory.exists()) {
if (!create) return null;
if (!directory.mkdir()) {
LOG.error("Cannot create directory: " + directory.getAbsolutePath());
return null;
}
}
return directory;
}
@Override
public void addEditorColorsListener(@NotNull EditorColorsListener listener) {
myListeners.addListener(listener);
}
@Override
public void addEditorColorsListener(@NotNull EditorColorsListener listener, @NotNull Disposable disposable) {
myListeners.addListener(listener, disposable);
}
@Override
public void removeEditorColorsListener(@NotNull EditorColorsListener listener) {
myListeners.removeListener(listener);
}
@Override
public void setUseOnlyMonospacedFonts(boolean b) {
USE_ONLY_MONOSPACED_FONTS = b;
}
@Override
public boolean isUseOnlyMonospacedFonts() {
return USE_ONLY_MONOSPACED_FONTS;
}
@Override
public String getExternalFileName() {
return "colors.scheme";
}
@Override
@NotNull
public File[] getExportFiles() {
return new File[]{getColorsDir(true), PathManager.getOptionsFile(this)};
}
@Override
@NotNull
public String getPresentableName() {
return OptionsBundle.message("options.color.schemes.presentable.name");
}
@Override
public void readExternal(Element parentNode) throws InvalidDataException {
DefaultJDOMExternalizer.readExternal(this, parentNode);
Element element = parentNode.getChild(NODE_NAME);
if (element != null) {
String name = element.getAttributeValue(NAME_ATTR);
if (StringUtil.isNotEmpty(name)) {
myGlobalSchemeName = name;
}
}
EditorColorsScheme globalScheme =
myGlobalSchemeName != null ? mySchemesManager.findSchemeByName(myGlobalSchemeName) : myDefaultColorSchemesManager.getAllSchemes()[0];
setGlobalSchemeInner(globalScheme);
}
@Override
public void writeExternal(Element parentNode) throws WriteExternalException {
DefaultJDOMExternalizer.writeExternal(this, parentNode);
if (mySchemesManager.getCurrentScheme() != null) {
Element element = new Element(NODE_NAME);
element.setAttribute(NAME_ATTR, mySchemesManager.getCurrentScheme().getName());
parentNode.addContent(element);
}
}
@Override
public boolean isDefaultScheme(EditorColorsScheme scheme) {
return scheme instanceof DefaultColorsScheme;
}
public SchemesManager<EditorColorsScheme, EditorColorsSchemeImpl> getSchemesManager() {
return mySchemesManager;
}
@Override
@NotNull
public String getComponentName() {
return "EditorColorsManagerImpl";
}
private final class MySchemeProcessor extends BaseSchemeProcessor<EditorColorsSchemeImpl> implements SchemeExtensionProvider {
@Override
public EditorColorsSchemeImpl readScheme(@NotNull final Document document)
throws InvalidDataException {
return loadSchemeFromDocument(document, true);
}
@Override
public Element writeScheme(@NotNull final EditorColorsSchemeImpl scheme) {
Element root = new Element(SCHEME_NODE_NAME);
try {
scheme.writeExternal(root);
}
catch (WriteExternalException e) {
LOG.error(e);
return null;
}
return root;
}
@Override
public boolean shouldBeSaved(@NotNull final EditorColorsSchemeImpl scheme) {
return !(scheme instanceof ReadOnlyColorsScheme);
}
@Override
public void onCurrentSchemeChanged(final Scheme newCurrentScheme) {
fireChanges(mySchemesManager.getCurrentScheme());
}
@NotNull
@Override
public String getSchemeExtension() {
return FILE_EXT;
}
@Override
public boolean isUpgradeNeeded() {
return true;
}
}
}