/* * Copyright 2013-2016 Sergey Ignatov, Alexander Zolotov, Florin Patan * * 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.goide.configuration; import com.goide.sdk.GoSdkService; import com.goide.sdk.GoSdkUtil; import com.goide.sdk.GoSmallIDEsSdkService; import com.intellij.openapi.Disposable; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.fileChooser.FileChooserDescriptor; import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.options.Configurable; import com.intellij.openapi.options.ConfigurationException; import com.intellij.openapi.options.SearchableConfigurable; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.*; import com.intellij.openapi.roots.libraries.Library; import com.intellij.openapi.roots.libraries.LibraryTable; import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar; import com.intellij.openapi.ui.ComponentWithBrowseButton; import com.intellij.openapi.ui.TextComponentAccessor; import com.intellij.openapi.ui.TextFieldWithBrowseButton; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.ui.DocumentAdapter; import com.intellij.ui.JBCardLayout; import com.intellij.ui.JBColor; import com.intellij.ui.components.JBLabel; import com.intellij.util.Alarm; import com.intellij.util.ArrayUtil; import com.intellij.util.ObjectUtils; import com.intellij.util.ui.AsyncProcessIcon; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.event.DocumentEvent; import java.awt.*; public class GoSdkConfigurable implements SearchableConfigurable, Configurable.NoScroll { private static final String VERSION_GETTING = "VERSION_GETTING_CARD"; private static final String VERSION_RESULT = "VERSION_RESULT_CARD"; @NotNull private final Project myProject; @NotNull private final Disposable myDisposable = Disposer.newDisposable(); @NotNull private final Alarm myAlarm = new Alarm(Alarm.ThreadToUse.POOLED_THREAD, myDisposable); private JPanel myComponent; private TextFieldWithBrowseButton mySdkPathField; private JPanel myVersionPanel; private JBLabel myVersionLabel; private Color myDefaultLabelColor; public GoSdkConfigurable(@NotNull Project project, boolean dialogMode) { myProject = project; if (dialogMode) { myComponent.setPreferredSize(new Dimension(400, -1)); } FileChooserDescriptor chooserDescriptor = FileChooserDescriptorFactory.createSingleFolderDescriptor().withTitle("Select GOROOT"); mySdkPathField.addBrowseFolderListener(myProject, new MyBrowseFolderListener(chooserDescriptor)); listenForPathUpdate(); Disposer.register(myDisposable, mySdkPathField); } @Override public void apply() throws ConfigurationException { ApplicationManager.getApplication().runWriteAction(() -> { if (myProject.isDefault() || myProject.isDisposed()) { return; } LibraryTable table = LibraryTablesRegistrar.getInstance().getLibraryTable(myProject); Library get = table.getLibraryByName(GoSmallIDEsSdkService.LIBRARY_NAME); Library lib = get != null ? get : table.createLibrary(GoSmallIDEsSdkService.LIBRARY_NAME); Library.ModifiableModel libraryModel = lib.getModifiableModel(); String libUrl = ArrayUtil.getFirstElement(lib.getUrls(OrderRootType.CLASSES)); if (libUrl != null) { libraryModel.removeRoot(libUrl, OrderRootType.CLASSES); } String sdkPath = GoSdkUtil.adjustSdkPath(mySdkPathField.getText()); String versionString = GoSdkUtil.retrieveGoVersion(sdkPath); boolean toRemove = StringUtil.isEmpty(sdkPath) || versionString == null; if (!toRemove) { for (VirtualFile file : GoSdkUtil.getSdkDirectoriesToAttach(sdkPath, versionString)) { libraryModel.addRoot(file, OrderRootType.CLASSES); } } libraryModel.commit(); if (toRemove) { updateModules(myProject, lib, true); table.removeLibrary(lib); } table.getModifiableModel().commit(); if (!toRemove) { updateModules(myProject, lib, false); } GoSdkService.getInstance(myProject).incModificationCount(); }); } @Override public void reset() { mySdkPathField.setText(StringUtil.notNullize(GoSdkService.getInstance(myProject).getSdkHomePath(null))); } @Override public boolean isModified() { String currentPath = StringUtil.notNullize(GoSdkService.getInstance(myProject).getSdkHomePath(null)); return !GoSdkUtil.adjustSdkPath(mySdkPathField.getText()).equals(currentPath); } @NotNull @Override public String getId() { return "go.sdk"; } @Nullable @Override public Runnable enableSearch(String option) { return null; } @Nls @Override public String getDisplayName() { return "Go SDK"; } @Nullable @Override public String getHelpTopic() { return null; } @Nullable @Override public JComponent createComponent() { return myComponent; } private static void updateModules(@NotNull Project project, @NotNull Library lib, boolean remove) { Module[] modules = ModuleManager.getInstance(project).getModules(); for (Module module : modules) { ModifiableRootModel model = ModuleRootManager.getInstance(module).getModifiableModel(); if (!remove) { if (model.findLibraryOrderEntry(lib) == null) { LibraryOrderEntry entry = model.addLibraryEntry(lib); entry.setScope(DependencyScope.PROVIDED); } } else { LibraryOrderEntry entry = model.findLibraryOrderEntry(lib); if (entry != null) { model.removeOrderEntry(entry); } } model.commit(); } } private void asyncUpdateSdkVersion(@NotNull String sdkPath) { ApplicationManager.getApplication().assertIsDispatchThread(); ((CardLayout)myVersionPanel.getLayout()).show(myVersionPanel, VERSION_GETTING); if (!myAlarm.isDisposed()) { myAlarm.cancelAllRequests(); myAlarm.addRequest(() -> { String version = GoSdkUtil.retrieveGoVersion(GoSdkUtil.adjustSdkPath(sdkPath)); ApplicationManager.getApplication().invokeLater(() -> { if (!Disposer.isDisposed(myDisposable)) { setVersion(version); } }, ModalityState.any()); }, 100); } } private void setVersion(@Nullable String version) { if (version == null) { myVersionLabel.setText("N/A"); myVersionLabel.setForeground(JBColor.RED); } else { myVersionLabel.setText(version); myVersionLabel.setForeground(myDefaultLabelColor); } ((CardLayout)myVersionPanel.getLayout()).show(myVersionPanel, VERSION_RESULT); } private void createUIComponents() { myVersionLabel = new JBLabel(); myDefaultLabelColor = myVersionLabel.getForeground(); myVersionPanel = new JPanel(new JBCardLayout()); JPanel gettingVersionPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 0)); AsyncProcessIcon gettingVersionIcon = new AsyncProcessIcon("Getting Go version"); gettingVersionPanel.add(gettingVersionIcon); gettingVersionPanel.add(new JLabel("Getting...")); myVersionPanel.add(gettingVersionPanel, VERSION_GETTING); myVersionPanel.add(myVersionLabel, VERSION_RESULT); setVersion(null); } @Override public void disposeUIResources() { UIUtil.dispose(myVersionLabel); UIUtil.dispose(myVersionPanel); UIUtil.dispose(myComponent); myVersionLabel = null; myVersionPanel = null; myDefaultLabelColor = null; Disposer.dispose(myDisposable); } private class MyBrowseFolderListener extends ComponentWithBrowseButton.BrowseFolderActionListener<JTextField> { public MyBrowseFolderListener(@NotNull FileChooserDescriptor descriptor) { super("Select Go SDK Path", "", mySdkPathField, myProject, descriptor, TextComponentAccessor.TEXT_FIELD_WHOLE_TEXT); } @Nullable @Override protected VirtualFile getInitialFile() { return ObjectUtils.chooseNotNull(super.getInitialFile(), GoSdkUtil.suggestSdkDirectory()); } } private void listenForPathUpdate() { JTextField textField = mySdkPathField.getTextField(); Ref<String> prevPathRef = Ref.create(StringUtil.notNullize(textField.getText())); textField.getDocument().addDocumentListener(new DocumentAdapter() { @Override protected void textChanged(DocumentEvent e) { String sdkPath = StringUtil.notNullize(textField.getText()); String prevPath = prevPathRef.get(); if (!prevPath.equals(sdkPath)) { asyncUpdateSdkVersion(sdkPath); prevPathRef.set(sdkPath); } } }); } }