blob: 4dc31ed13646752fbcd5b513fdf969c572b2cc6d [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.netbeans.modules.php.editor.actions;
import java.awt.Dialog;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.prefs.Preferences;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.SwingUtilities;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
import org.netbeans.api.editor.EditorActionRegistration;
import org.netbeans.api.progress.BaseProgressUtils;
import org.netbeans.editor.BaseAction;
import org.netbeans.modules.editor.MainMenuAction;
import org.netbeans.modules.parsing.api.ParserManager;
import org.netbeans.modules.parsing.api.ResultIterator;
import org.netbeans.modules.parsing.api.Source;
import org.netbeans.modules.parsing.api.UserTask;
import org.netbeans.modules.parsing.spi.ParseException;
import org.netbeans.modules.parsing.spi.Parser.Result;
import org.netbeans.modules.php.api.PhpVersion;
import org.netbeans.modules.php.api.util.FileUtils;
import org.netbeans.modules.php.editor.CodeUtils;
import org.netbeans.modules.php.editor.actions.ImportData.ItemVariant;
import org.netbeans.modules.php.editor.api.ElementQuery.Index;
import org.netbeans.modules.php.editor.indent.CodeStyle;
import org.netbeans.modules.php.editor.model.ModelUtils;
import org.netbeans.modules.php.editor.model.NamespaceScope;
import org.netbeans.modules.php.editor.parser.PHPParseResult;
import org.openide.DialogDescriptor;
import org.openide.DialogDisplayer;
import org.openide.filesystems.FileObject;
import org.openide.util.Exceptions;
import org.openide.util.HelpCtx;
import org.openide.util.NbBundle.Messages;
import org.openide.util.NbPreferences;
import org.openide.util.RequestProcessor;
/**
*
* @author Ondrej Brejla <obrejla@netbeans.org>
*/
@Messages({
"FixUsesLabel=Fix Uses...",
"LongName=Fix Uses in Current Namespace"
})
@EditorActionRegistration(
name = FixUsesAction.ACTION_NAME,
mimeType = FileUtils.PHP_MIME_TYPE,
shortDescription = "Fixes use statements.",
popupText = "#FixUsesLabel"
)
public class FixUsesAction extends BaseAction {
static final String ACTION_NAME = "fix-uses"; //NOI18N
private static final String PREFERENCES_NODE_KEY = FixUsesAction.class.getName();
private static final String KEY_REMOVE_UNUSED_USES = "remove.unused.uses"; //NOI18N
private static final boolean REMOVE_UNUSED_USES_DEFAULT = true;
public FixUsesAction() {
super(MAGIC_POSITION_RESET | UNDO_MERGE_RESET);
}
@Override
public void actionPerformed(ActionEvent evt, final JTextComponent target) {
if (target != null) {
final int caretPosition = target.getCaretPosition();
final AtomicBoolean cancel = new AtomicBoolean();
final AtomicReference<ImportData> importData = new AtomicReference<>();
final UserTask task = new UserTask() {
@Override
public void run(ResultIterator resultIterator) throws Exception {
Result parserResult = resultIterator.getParserResult();
if (parserResult instanceof PHPParseResult) {
if (cancel.get()) {
return;
}
final ImportData data = computeUses((PHPParseResult) parserResult, caretPosition);
if (cancel.get()) {
return;
}
if (data.shouldShowUsesPanel) {
if (!cancel.get()) {
importData.set(data);
}
} else {
performFixUses((PHPParseResult) parserResult, data, data.getDefaultVariants(), isRemoveUnusedUses());
}
}
}
};
BaseProgressUtils.runOffEventDispatchThread(new Runnable() {
@Override
public void run() {
try {
ParserManager.parse(Collections.singleton(Source.create(target.getDocument())), task);
} catch (ParseException ex) {
Exceptions.printStackTrace(ex);
}
}
}, Bundle.LongName(), cancel, false);
if (importData.get() != null && !cancel.get()) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
showFixUsesDialog(target, importData.get());
}
});
}
}
}
private static Preferences getPreferences() {
return NbPreferences.forModule(FixUsesAction.class).node(PREFERENCES_NODE_KEY);
}
private static boolean isRemoveUnusedUses() {
return getPreferences().getBoolean(KEY_REMOVE_UNUSED_USES, REMOVE_UNUSED_USES_DEFAULT);
}
private static void setRemoveUnusedUses(final boolean removeUnusedUses) {
getPreferences().putBoolean(KEY_REMOVE_UNUSED_USES, removeUnusedUses);
}
private static ImportData computeUses(final PHPParseResult parserResult, final int caretPosition) {
Map<String, List<UsedNamespaceName>> filteredExistingNames = new UsedNamesCollector(parserResult, caretPosition).collectNames();
Index index = parserResult.getModel().getIndexScope().getIndex();
NamespaceScope namespaceScope = ModelUtils.getNamespaceScope(parserResult.getModel().getFileScope(), caretPosition);
assert namespaceScope != null;
ImportData importData = new ImportDataCreator(filteredExistingNames, index, namespaceScope.getNamespaceName(), createOptions(parserResult)).create();
importData.caretPosition = caretPosition;
return importData;
}
private static void performFixUses(
final PHPParseResult parserResult,
final ImportData importData,
final List<ImportData.ItemVariant> selections,
final boolean removeUnusedUses) {
new FixUsesPerformer(parserResult, importData, selections, removeUnusedUses, createOptions(parserResult)).perform();
}
private static Options createOptions(final PHPParseResult parserResult) {
Document document = parserResult.getSnapshot().getSource().getDocument(false);
CodeStyle codeStyle = CodeStyle.get(document);
return new Options(codeStyle, parserResult.getModel().getFileScope().getFileObject());
}
private static final RequestProcessor WORKER = new RequestProcessor(FixUsesAction.class.getName(), 1);
@Messages({
"LBL_Ok=Ok",
"LBL_Cancel=Cancel"
})
private static void showFixUsesDialog(final JTextComponent target, final ImportData importData) {
final FixDuplicateImportStmts panel = new FixDuplicateImportStmts();
panel.initPanel(importData, isRemoveUnusedUses());
final JButton ok = new JButton(Bundle.LBL_Ok());
final JButton cancel = new JButton(Bundle.LBL_Cancel());
final AtomicBoolean stop = new AtomicBoolean();
DialogDescriptor dd = new DialogDescriptor(panel, Bundle.LongName(), true, new Object[] {ok, cancel}, ok, DialogDescriptor.DEFAULT_ALIGN,
HelpCtx.DEFAULT_HELP, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
}
}, true);
final Dialog d = DialogDisplayer.getDefault().createDialog(dd);
ok.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
ok.setEnabled(false);
final List<ItemVariant> selections = panel.getSelections();
final boolean removeUnusedUses = panel.getRemoveUnusedImports();
WORKER.post(new Runnable() {
@Override
public void run() {
try {
ParserManager.parse(Collections.singleton(Source.create(target.getDocument())), new UserTask() {
@Override
public void run(ResultIterator resultIterator) throws Exception {
Result parserResult = resultIterator.getParserResult();
if (parserResult instanceof PHPParseResult) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
cancel.setEnabled(false);
((JDialog) d).setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
}
});
if (stop.get()) {
return;
}
performFixUses((PHPParseResult) parserResult, importData, selections, removeUnusedUses);
}
}
});
} catch (ParseException ex) {
Exceptions.printStackTrace(ex);
}
setRemoveUnusedUses(removeUnusedUses);
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
d.setVisible(false);
}
});
}
});
}
});
cancel.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
stop.set(true);
d.setVisible(false);
}
});
d.setVisible(true);
d.dispose();
}
public static final class GlobalAction extends MainMenuAction {
public GlobalAction() {
super();
setMenu();
}
@Override
protected String getMenuItemText() {
return Bundle.FixUsesLabel();
}
@Override
protected String getActionName() {
return ACTION_NAME;
}
}
public static class Options {
private final boolean preferFullyQualifiedNames;
private final boolean preferMultipleUseStatementsCombined;
private final boolean preferGroupUses;
private final boolean startUseWithNamespaceSeparator;
private final boolean aliasesCapitalsOfNamespaces;
private final PhpVersion phpVersion;
// for unit tests
Options(
boolean preferFullyQualifiedNames,
boolean preferMultipleUseStatementsCombined,
boolean preferGroupUses,
boolean startUseWithNamespaceSeparator,
boolean aliasesCapitalsOfNamespaces,
PhpVersion phpVersion) {
this.preferFullyQualifiedNames = preferFullyQualifiedNames;
this.preferMultipleUseStatementsCombined = preferMultipleUseStatementsCombined;
this.preferGroupUses = preferGroupUses;
this.startUseWithNamespaceSeparator = startUseWithNamespaceSeparator;
this.aliasesCapitalsOfNamespaces = aliasesCapitalsOfNamespaces;
this.phpVersion = phpVersion;
}
// legacy, for unit tests
Options(
boolean preferFullyQualifiedNames,
boolean preferMultipleUseStatementsCombined,
boolean startUseWithNamespaceSeparator,
boolean aliasesCapitalsOfNamespaces,
boolean isPhp56OrGreater) {
this(preferFullyQualifiedNames, preferMultipleUseStatementsCombined, false, startUseWithNamespaceSeparator, aliasesCapitalsOfNamespaces,
isPhp56OrGreater ? PhpVersion.PHP_56 : PhpVersion.PHP_5);
}
// legacy, for unit tests
Options(
boolean preferFullyQualifiedNames,
boolean preferMultipleUseStatementsCombined,
boolean preferGroupUses,
boolean startUseWithNamespaceSeparator,
boolean aliasesCapitalsOfNamespaces,
boolean isPhp56OrGreater) {
this(preferFullyQualifiedNames, preferMultipleUseStatementsCombined, preferGroupUses, startUseWithNamespaceSeparator, aliasesCapitalsOfNamespaces,
isPhp56OrGreater ? PhpVersion.PHP_56 : PhpVersion.PHP_5);
}
public Options(CodeStyle codeStyle, FileObject fileObject) {
this.preferFullyQualifiedNames = codeStyle.preferFullyQualifiedNames();
this.preferMultipleUseStatementsCombined = codeStyle.preferMultipleUseStatementsCombined();
this.preferGroupUses = codeStyle.preferGroupUses();
this.startUseWithNamespaceSeparator = codeStyle.startUseWithNamespaceSeparator();
this.aliasesCapitalsOfNamespaces = codeStyle.aliasesFromCapitalsOfNamespaces();
this.phpVersion = CodeUtils.getPhpVersion(fileObject);
}
public boolean preferFullyQualifiedNames() {
return preferFullyQualifiedNames;
}
public boolean preferMultipleUseStatementsCombined() {
return preferMultipleUseStatementsCombined;
}
public boolean preferGroupUses() {
return preferGroupUses;
}
public boolean startUseWithNamespaceSeparator() {
return startUseWithNamespaceSeparator;
}
public boolean aliasesCapitalsOfNamespaces() {
return aliasesCapitalsOfNamespaces;
}
public PhpVersion getPhpVersion() {
return phpVersion;
}
}
}