Merge remote-tracking branch 'origin/master'

This commit is contained in:
Ekaterina Tuzova
2015-02-17 12:27:40 +03:00
65 changed files with 1907 additions and 1051 deletions
+13
View File
@@ -0,0 +1,13 @@
# IntelliJ IDEA Community Edition
### Building and Running from the IDE
To develop IntelliJ IDEA, you can use either IntelliJ IDEA Community Edition or IntelliJ IDEA Ultimate. To build and run the code:
* Run **getPlugins.sh** / **getPlugins.bat** from the project root directory to check out additional modules.
* Make sure you have the **Groovy** plugin enabled. Parts of IntelliJ IDEA are written in Groovy, and you will get compilation errors if you don't have the plugin enabled.
* Make sure you have the **UI Designer** plugin enabled. Most of IntelliJ IDEA's UI is built using the **UI Designer**, and the version you build will not run correctly if you don't have the plugin enabled.
* Open the project
* Configure a JSDK named "**IDEA jdk**" (case sensitive), pointing to an installation of JDK 1.6
* Unless you're running on a Mac with an Apple JDK, add <JDK_HOME>/lib/tools.jar to the set of "**IDEA jdk**" jars.
* Use Build | Make Project to build the code.
* To run the code, use the provided shared run configuration "**IDEA**".
You can find other useful information at [http://www.jetbrains.org](http://www.jetbrains.org)
+1
View File
@@ -248,6 +248,7 @@ binding.setVariable("commonJvmArgsForTests", {
if (debugPort != null) jdwp += ",address=$debugPort"
return [
"-ea",
"-Dio.netty.leakDetectionLevel=PARANOID",
"-server",
"-Xbootclasspath/p:${projectBuilder.moduleOutput(findModule("boot"))}",
"-XX:+HeapDumpOnOutOfMemoryError",
@@ -15,18 +15,30 @@
*/
package com.intellij.openapi.roots.ui.configuration;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.actionSystem.CustomShortcutSet;
import com.intellij.openapi.project.ProjectBundle;
import com.intellij.openapi.roots.SourceFolder;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.ui.roots.IconActionComponent;
import com.intellij.util.ui.FormBuilder;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.model.JpsDummyElement;
import org.jetbrains.jps.model.java.JavaModuleSourceRootTypes;
import org.jetbrains.jps.model.java.JavaResourceRootProperties;
import org.jetbrains.jps.model.module.JpsModuleSourceRootType;
import javax.swing.*;
import java.awt.*;
/**
* @author nik
*/
public abstract class JavaResourceRootEditHandlerBase extends ModuleSourceRootEditHandler<JpsDummyElement> {
protected JavaResourceRootEditHandlerBase(JpsModuleSourceRootType<JpsDummyElement> rootType) {
public abstract class JavaResourceRootEditHandlerBase extends ModuleSourceRootEditHandler<JavaResourceRootProperties> {
public JavaResourceRootEditHandlerBase(JpsModuleSourceRootType<JavaResourceRootProperties> rootType) {
super(rootType);
}
@@ -41,4 +53,103 @@ public abstract class JavaResourceRootEditHandlerBase extends ModuleSourceRootEd
public CustomShortcutSet getMarkRootShortcutSet() {
return null;
}
@NotNull
@Override
public Icon getRootIcon(@NotNull JavaResourceRootProperties properties) {
return properties.isForGeneratedSources() ? getGeneratedRootIcon() : getRootIcon();
}
@NotNull
protected Icon getGeneratedRootIcon() {
return getRootIcon();
}
@Nullable
@Override
public String getPropertiesString(@NotNull JavaResourceRootProperties properties) {
StringBuilder buffer = new StringBuilder();
if (properties.isForGeneratedSources()) {
buffer.append(" [generated]");
}
String relativeOutputPath = properties.getRelativeOutputPath();
if (!relativeOutputPath.isEmpty()) {
buffer.append(" (").append(relativeOutputPath).append(")");
}
return buffer.length() > 0 ? buffer.toString() : null;
}
@Nullable
@Override
public JComponent createPropertiesEditor(@NotNull final SourceFolder folder,
@NotNull final JComponent parentComponent,
@NotNull final ContentRootPanel.ActionCallback callback) {
final IconActionComponent iconComponent = new IconActionComponent(AllIcons.Modules.SetPackagePrefix,
AllIcons.Modules.SetPackagePrefixRollover,
ProjectBundle.message("module.paths.edit.properties.tooltip"),
new Runnable() {
@Override
public void run() {
JavaResourceRootProperties properties = folder.getJpsElement().getProperties( JavaModuleSourceRootTypes.RESOURCES);
assert properties != null;
ResourceRootPropertiesDialog
dialog = new ResourceRootPropertiesDialog(parentComponent, properties);
if (dialog.showAndGet()) {
callback.onSourceRootPropertiesChanged(folder);
}
}
});
final JPanel panel = new JPanel(new BorderLayout());
panel.setOpaque(false);
panel.add(iconComponent, BorderLayout.CENTER);
panel.add(Box.createHorizontalStrut(3), BorderLayout.EAST);
return panel;
}
private static class ResourceRootPropertiesDialog extends DialogWrapper {
private final JTextField myRelativeOutputPathField;
private final JCheckBox myIsGeneratedCheckBox;
private final JPanel myMainPanel;
@NotNull private final JavaResourceRootProperties myProperties;
private ResourceRootPropertiesDialog(@NotNull JComponent parentComponent, @NotNull JavaResourceRootProperties properties) {
super(parentComponent, true);
myProperties = properties;
setTitle(ProjectBundle.message("module.paths.edit.properties.title"));
myRelativeOutputPathField = new JTextField();
myIsGeneratedCheckBox = new JCheckBox(UIUtil.replaceMnemonicAmpersand("For &generated resources"));
myMainPanel = FormBuilder.createFormBuilder()
.addLabeledComponent("Relative output &path:", myRelativeOutputPathField)
.addComponent(myIsGeneratedCheckBox)
.getPanel();
myRelativeOutputPathField.setText(myProperties.getRelativeOutputPath());
myRelativeOutputPathField.setColumns(25);
myIsGeneratedCheckBox.setSelected(myProperties.isForGeneratedSources());
init();
}
@Nullable
@Override
public JComponent getPreferredFocusedComponent() {
return myRelativeOutputPathField;
}
@Override
protected void doOKAction() {
myProperties.setRelativeOutputPath(normalizePath(myRelativeOutputPathField.getText()));
myProperties.setForGeneratedSources(myIsGeneratedCheckBox.isSelected());
super.doOKAction();
}
@NotNull
private static String normalizePath(String path) {
return StringUtil.trimEnd(StringUtil.trimStart(FileUtil.toSystemIndependentName(path.trim()), "/"), "/");
}
@Nullable
@Override
protected JComponent createCenterPanel() {
return myMainPanel;
}
}
}
@@ -12,6 +12,7 @@ import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.model.java.JavaModuleSourceRootTypes;
import org.jetbrains.jps.model.java.JavaResourceRootProperties;
import org.jetbrains.jps.model.java.JavaSourceRootProperties;
import java.util.ArrayList;
@@ -51,7 +52,8 @@ public class JavaProjectRootsUtil {
private static boolean isForGeneratedSources(SourceFolder sourceFolder) {
JavaSourceRootProperties properties = sourceFolder.getJpsElement().getProperties(JavaModuleSourceRootTypes.SOURCES);
return properties != null && properties.isForGeneratedSources();
JavaResourceRootProperties resourceProperties = sourceFolder.getJpsElement().getProperties(JavaModuleSourceRootTypes.RESOURCES);
return properties != null && properties.isForGeneratedSources() || resourceProperties != null && resourceProperties.isForGeneratedSources();
}
public static boolean isInGeneratedCode(@NotNull VirtualFile file, @NotNull Project project) {
@@ -241,8 +241,11 @@ public class JavaSdkImpl extends JavaSdk {
final FileChooserDescriptor descriptor = new FileChooserDescriptor(baseDescriptor) {
@Override
public void validateSelectedFiles(VirtualFile[] files) throws Exception {
if (files.length > 0 && JrtFileSystem.isModularJdk(files[0].getPath()) && !JrtFileSystem.isSupported()) {
throw new Exception(LangBundle.message("jrt.not.available.message"));
if (files.length > 0 && !JrtFileSystem.isSupported()) {
String path = files[0].getPath();
if (JrtFileSystem.isModularJdk(path) || JrtFileSystem.isModularJdk(adjustSelectedSdkHome(path))) {
throw new Exception(LangBundle.message("jrt.not.available.message"));
}
}
baseDescriptor.validateSelectedFiles(files);
}
@@ -32,10 +32,7 @@ import org.jetbrains.jps.cmdline.ProjectDescriptor;
import org.jetbrains.jps.indices.IgnoredFileIndex;
import org.jetbrains.jps.indices.ModuleExcludeIndex;
import org.jetbrains.jps.model.JpsModel;
import org.jetbrains.jps.model.java.JavaResourceRootType;
import org.jetbrains.jps.model.java.JavaSourceRootProperties;
import org.jetbrains.jps.model.java.JavaSourceRootType;
import org.jetbrains.jps.model.java.JpsJavaExtensionService;
import org.jetbrains.jps.model.java.*;
import org.jetbrains.jps.model.module.JpsModule;
import org.jetbrains.jps.model.module.JpsModuleSourceRoot;
import org.jetbrains.jps.model.module.JpsTypedModuleSourceRoot;
@@ -101,10 +98,11 @@ public final class ResourcesTarget extends JVMModuleBuildTarget<ResourceRootDesc
}
JavaResourceRootType resourceType = isTests() ? JavaResourceRootType.TEST_RESOURCE : JavaResourceRootType.RESOURCE;
for (JpsModuleSourceRoot root : myModule.getSourceRoots(resourceType)) {
for (JpsTypedModuleSourceRoot<JavaResourceRootProperties> root : myModule.getSourceRoots(resourceType)) {
if (!isExcludedFromCompilation(excludedRootProviders, root)) {
File rootFile = root.getFile();
roots.add(new ResourceRootDescriptor(rootFile, this, "", computeRootExcludes(rootFile, index)));
String relativeOutputPath = root.getProperties().getRelativeOutputPath();
roots.add(new ResourceRootDescriptor(rootFile, this, relativeOutputPath.replace('/', '.'), computeRootExcludes(rootFile, index)));
}
}
@@ -14,6 +14,12 @@
* limitations under the License.
*/
package org.jetbrains.jps.builders.rebuild
import org.jetbrains.jps.util.JpsPathUtil
import com.intellij.util.PathUtil
import org.jetbrains.jps.model.java.JavaResourceRootType
import org.jetbrains.jps.model.java.JpsJavaExtensionService
/**
* @author nik
*/
@@ -71,4 +77,17 @@ public class ModuleRebuildTest: JpsRebuildTestCase() {
}
})
}
fun testResourceRootWithRelativeOutputPath() {
val m = addModule("m")
val res = PathUtil.getParentPath(createFile("res/a.txt", "42"))
val url = JpsPathUtil.pathToUrl(res)
m.addSourceRoot(url, JavaResourceRootType.RESOURCE, JpsJavaExtensionService.getInstance().createResourceRootProperties("foo", false))
rebuild()
assertOutput(getAbsolutePath("out/production/m"), fs {
dir("foo") {
file("a.txt", "42")
}
})
}
}
@@ -0,0 +1,71 @@
/*
* Copyright 2000-2015 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.jps.model.java;
import com.intellij.openapi.util.Comparing;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jps.model.ex.JpsElementBase;
/**
* @author nik
*/
public class JavaResourceRootProperties extends JpsElementBase<JavaResourceRootProperties> {
private String myRelativeOutputPath = "";
private boolean myForGeneratedSources;
public JavaResourceRootProperties(@NotNull String relativeOutputPath, boolean forGeneratedSources) {
myRelativeOutputPath = relativeOutputPath;
myForGeneratedSources = forGeneratedSources;
}
/**
* @return relative path to the target directory under the module output directory for resource files from this root
*/
@NotNull
public String getRelativeOutputPath() {
return myRelativeOutputPath;
}
@NotNull
@Override
public JavaResourceRootProperties createCopy() {
return new JavaResourceRootProperties(myRelativeOutputPath, myForGeneratedSources);
}
public boolean isForGeneratedSources() {
return myForGeneratedSources;
}
public void setRelativeOutputPath(@NotNull String relativeOutputPath) {
if (!Comparing.equal(myRelativeOutputPath, relativeOutputPath)) {
myRelativeOutputPath = relativeOutputPath;
fireElementChanged();
}
}
public void setForGeneratedSources(boolean forGeneratedSources) {
if (myForGeneratedSources != forGeneratedSources) {
myForGeneratedSources = forGeneratedSources;
fireElementChanged();
}
}
@Override
public void applyChanges(@NotNull JavaResourceRootProperties modified) {
setRelativeOutputPath(modified.myRelativeOutputPath);
setForGeneratedSources(modified.myForGeneratedSources);
}
}
@@ -16,15 +16,14 @@
package org.jetbrains.jps.model.java;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jps.model.JpsDummyElement;
import org.jetbrains.jps.model.JpsElementFactory;
import org.jetbrains.jps.model.ex.JpsElementTypeWithDummyProperties;
import org.jetbrains.jps.model.ex.JpsElementTypeBase;
import org.jetbrains.jps.model.module.JpsModuleSourceRootType;
/**
* @author nik
*/
public class JavaResourceRootType extends JpsElementTypeWithDummyProperties implements JpsModuleSourceRootType<JpsDummyElement> {
public class JavaResourceRootType extends JpsElementTypeBase<JavaResourceRootProperties> implements
JpsModuleSourceRootType<JavaResourceRootProperties> {
public static final JavaResourceRootType RESOURCE = new JavaResourceRootType();
public static final JavaResourceRootType TEST_RESOURCE = new JavaResourceRootType();
@@ -33,7 +32,7 @@ public class JavaResourceRootType extends JpsElementTypeWithDummyProperties impl
@NotNull
@Override
public JpsDummyElement createDefaultProperties() {
return JpsElementFactory.getInstance().createDummyElement();
public JavaResourceRootProperties createDefaultProperties() {
return JpsJavaExtensionService.getInstance().createResourceRootProperties("", false);
}
}
@@ -121,4 +121,7 @@ public abstract class JpsJavaExtensionService {
@NotNull
public abstract JavaSourceRootProperties createSourceRootProperties(@NotNull String packagePrefix);
@NotNull
public abstract JavaResourceRootProperties createResourceRootProperties(@NotNull String relativeOutputPath, boolean forGeneratedResource);
}
@@ -188,6 +188,12 @@ public class JpsJavaExtensionServiceImpl extends JpsJavaExtensionService {
return createSourceRootProperties(packagePrefix, false);
}
@NotNull
@Override
public JavaResourceRootProperties createResourceRootProperties(@NotNull String relativeOutputPath, boolean forGeneratedResource) {
return new JavaResourceRootProperties(relativeOutputPath, forGeneratedResource);
}
@Override
@NotNull
public JpsProductionModuleOutputPackagingElement createProductionModuleOutput(@NotNull JpsModuleReference moduleReference) {
@@ -19,7 +19,9 @@ import com.intellij.openapi.util.JDOMUtil;
import com.intellij.openapi.util.text.StringUtil;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jps.model.*;
import org.jetbrains.jps.model.JpsElementFactory;
import org.jetbrains.jps.model.JpsProject;
import org.jetbrains.jps.model.JpsUrlList;
import org.jetbrains.jps.model.java.*;
import org.jetbrains.jps.model.library.JpsOrderRootType;
import org.jetbrains.jps.model.module.JpsDependencyElement;
@@ -32,7 +34,6 @@ import org.jetbrains.jps.model.serialization.artifact.JpsPackagingElementSeriali
import org.jetbrains.jps.model.serialization.java.compiler.*;
import org.jetbrains.jps.model.serialization.library.JpsLibraryRootTypeSerializer;
import org.jetbrains.jps.model.serialization.module.JpsModuleRootModelSerializer;
import org.jetbrains.jps.model.serialization.module.JpsModuleSourceRootDummyPropertiesSerializer;
import org.jetbrains.jps.model.serialization.module.JpsModuleSourceRootPropertiesSerializer;
import java.util.Arrays;
@@ -56,6 +57,7 @@ public class JpsJavaModelSerializerExtension extends JpsModelSerializerExtension
private static final String JAVADOC_PATHS_TAG = "javadoc-paths";
private static final String MODULE_LANGUAGE_LEVEL_ATTRIBUTE = "LANGUAGE_LEVEL";
public static final String ROOT_TAG = "root";
private static final String RELATIVE_OUTPUT_PATH_ATTRIBUTE = "relativeOutputPath";
private static final String IS_GENERATED_ATTRIBUTE = "generated";
public static final JavaSourceRootPropertiesSerializer JAVA_SOURCE_ROOT_PROPERTIES_SERIALIZER =
new JavaSourceRootPropertiesSerializer(JavaSourceRootType.SOURCE, JpsModuleRootModelSerializer.JAVA_SOURCE_ROOT_TYPE_ID);
@@ -88,8 +90,8 @@ public class JpsJavaModelSerializerExtension extends JpsModelSerializerExtension
public List<? extends JpsModuleSourceRootPropertiesSerializer<?>> getModuleSourceRootPropertiesSerializers() {
return Arrays.asList(JAVA_SOURCE_ROOT_PROPERTIES_SERIALIZER,
new JavaSourceRootPropertiesSerializer(JavaSourceRootType.TEST_SOURCE, JpsModuleRootModelSerializer.JAVA_TEST_ROOT_TYPE_ID),
new JpsModuleSourceRootDummyPropertiesSerializer(JavaResourceRootType.RESOURCE, "java-resource"),
new JpsModuleSourceRootDummyPropertiesSerializer(JavaResourceRootType.TEST_RESOURCE, "java-test-resource"));
new JavaResourceRootPropertiesSerializer(JavaResourceRootType.RESOURCE, "java-resource"),
new JavaResourceRootPropertiesSerializer(JavaResourceRootType.TEST_RESOURCE, "java-test-resource"));
}
@Override
@@ -328,4 +330,28 @@ public class JpsJavaModelSerializerExtension extends JpsModelSerializerExtension
}
}
}
private static class JavaResourceRootPropertiesSerializer extends JpsModuleSourceRootPropertiesSerializer<JavaResourceRootProperties> {
private JavaResourceRootPropertiesSerializer(JpsModuleSourceRootType<JavaResourceRootProperties> type, String typeId) {
super(type, typeId);
}
@Override
public JavaResourceRootProperties loadProperties(@NotNull Element sourceRootTag) {
String relativeOutputPath = StringUtil.notNullize(sourceRootTag.getAttributeValue(RELATIVE_OUTPUT_PATH_ATTRIBUTE));
boolean isGenerated = Boolean.parseBoolean(sourceRootTag.getAttributeValue(IS_GENERATED_ATTRIBUTE));
return getService().createResourceRootProperties(relativeOutputPath, isGenerated);
}
@Override
public void saveProperties(@NotNull JavaResourceRootProperties properties, @NotNull Element sourceRootTag) {
String relativeOutputPath = properties.getRelativeOutputPath();
if (!relativeOutputPath.isEmpty()) {
sourceRootTag.setAttribute(RELATIVE_OUTPUT_PATH_ATTRIBUTE, relativeOutputPath);
}
if (properties.isForGeneratedSources()) {
sourceRootTag.setAttribute(IS_GENERATED_ATTRIBUTE, Boolean.TRUE.toString());
}
}
}
}
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/gen" type="java-resource" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/gen-foo" type="java-resource" relativeOutputPath="foo" generated="true" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/resourceRoots.iml" filepath="$PROJECT_DIR$/resourceRoots.iml" />
</modules>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_6" assert-keyword="true" jdk-15="true" project-jdk-name="1.4" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>
@@ -151,11 +151,31 @@ public class JpsProjectSerializationTest extends JpsSerializationTestCase {
assertEquals("UTF-8", configuration.getEncoding(new File(getAbsolutePath("other"))));
}
public void testResourceRoots() {
String projectPath = "/jps/model-serialization/testData/resourceRoots/";
loadProject(projectPath + "resourceRoots.ipr");
JpsModule module = assertOneElement(myProject.getModules());
List<JpsModuleSourceRoot> roots = module.getSourceRoots();
assertSame(JavaSourceRootType.SOURCE, roots.get(0).getRootType());
checkResourceRoot(roots.get(1), false, "");
checkResourceRoot(roots.get(2), true, "");
checkResourceRoot(roots.get(3), true, "foo");
doTestSaveModule(module, projectPath + "resourceRoots.iml");
}
private static void checkResourceRoot(JpsModuleSourceRoot root, boolean forGenerated, String relativeOutput) {
assertSame(JavaResourceRootType.RESOURCE, root.getRootType());
JavaResourceRootProperties properties = root.getProperties(JavaResourceRootType.RESOURCE);
assertNotNull(properties);
assertEquals(forGenerated, properties.isForGeneratedSources());
assertEquals(relativeOutput, properties.getRelativeOutputPath());
}
public void testSaveProject() {
loadProject(SAMPLE_PROJECT_PATH);
List<JpsModule> modules = myProject.getModules();
doTestSaveModule(modules.get(0), "main.iml");
doTestSaveModule(modules.get(1), "util/util.iml");
doTestSaveModule(modules.get(0), SAMPLE_PROJECT_PATH + "/main.iml");
doTestSaveModule(modules.get(1), SAMPLE_PROJECT_PATH + "/util/util.iml");
//tod[nik] remember that test output root wasn't specified and doesn't save it to avoid unnecessary modifications of iml files
//doTestSaveModule(modules.get(2), "xxx/xxx.iml");
@@ -188,7 +208,7 @@ public class JpsProjectSerializationTest extends JpsSerializationTestCase {
try {
Element actual = JDomSerializationUtil.createComponentElement("NewModuleRootManager");
JpsModuleRootModelSerializer.saveRootModel(module, actual);
File imlFile = getFileInSampleProject(moduleFilePath);
File imlFile = new File(getTestDataFileAbsolutePath(moduleFilePath));
Element rootElement = loadModuleRootTag(imlFile);
Element expected = JDomSerializationUtil.findComponent(rootElement, "NewModuleRootManager");
PlatformTestUtil.assertElementsEqual(expected, actual);
@@ -40,6 +40,9 @@ public abstract class DiffContentFactory {
@NotNull
public abstract EmptyContent createEmpty();
@NotNull
public abstract DocumentContent create(@NotNull String text);
@NotNull
public abstract DocumentContent create(@NotNull String text, @Nullable FileType type);
@@ -58,8 +61,11 @@ public abstract class DiffContentFactory {
@NotNull
public abstract DiffContent createClipboardContent();
/**
* @param referenceContent used to detect FileType and proper highlighting for clipboard content
*/
@NotNull
public abstract DocumentContent createClipboardContent(@NotNull DocumentContent mainContent);
public abstract DocumentContent createClipboardContent(@NotNull DocumentContent referenceContent);
@NotNull
public abstract DiffContent createBinary(@Nullable Project project,
@@ -20,7 +20,6 @@ import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.editor.impl.DocumentImpl;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.ide.CopyPasteManager;
@@ -47,6 +46,12 @@ public class DiffContentFactoryImpl extends DiffContentFactory {
return new EmptyContent();
}
@Override
@NotNull
public DocumentContent create(@NotNull String text) {
return create(text, null);
}
@Override
@NotNull
public DocumentContent create(@NotNull String text, @Nullable FileType type) {
@@ -97,14 +102,16 @@ public class DiffContentFactoryImpl extends DiffContentFactory {
@NotNull
public DiffContent createClipboardContent() {
String text = CopyPasteManager.getInstance().getContents(DataFlavor.stringFlavor);
return new DocumentContentImpl(new DocumentImpl(StringUtil.notNullize(text)));
Document document = EditorFactory.getInstance().createDocument(StringUtil.convertLineSeparators(StringUtil.notNullize(text)));
return new DocumentContentImpl(document); // TODO: show difference in line separators ?
}
@Override
@NotNull
public DocumentContent createClipboardContent(@NotNull DocumentContent mainContent) {
String text = CopyPasteManager.getInstance().getContents(DataFlavor.stringFlavor);
return new DocumentContentWrapper(mainContent, StringUtil.notNullize(text));
Document document = EditorFactory.getInstance().createDocument(StringUtil.convertLineSeparators(StringUtil.notNullize(text)));
return new DocumentContentWrapper(document, mainContent);
}
@Override
@@ -47,7 +47,7 @@ public class DiffRequestFactoryImpl extends DiffRequestFactory {
@NotNull
public ContentDiffRequest createClipboardVsValue(@NotNull String value) {
DiffContent content1 = myContentFactory.createClipboardContent();
DiffContent content2 = myContentFactory.create(value, null);
DiffContent content2 = myContentFactory.create(value);
String title1 = DiffBundle.message("diff.content.clipboard.content.title");
String title2 = DiffBundle.message("diff.content.selected.value");
@@ -16,7 +16,6 @@
package com.intellij.diff.contents;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.vfs.VirtualFile;
@@ -30,9 +29,9 @@ public class DocumentContentWrapper implements DocumentContent {
@NotNull private final DocumentContent myBase;
@NotNull private final Document myDocument;
public DocumentContentWrapper(@NotNull DocumentContent base, @NotNull String text) {
public DocumentContentWrapper(@NotNull Document document, @NotNull DocumentContent base) {
myDocument = document;
myBase = base;
myDocument = EditorFactory.getInstance().createDocument(text);
}
@NotNull
@@ -109,9 +109,9 @@ public class ScratchFileServiceImpl extends ScratchFileService implements Persis
@Nullable
@Override
public RootType getRootType(@NotNull VirtualFile file) {
VirtualFile parent = file.getParent();
if (!(parent instanceof VirtualFileWithId)) return null;
RootType result = myIndex.getInfoForFile(parent);
VirtualFile directory = file.isDirectory() ? file : file.getParent();
if (!(directory instanceof VirtualFileWithId)) return null;
RootType result = myIndex.getInfoForFile(directory);
return result == NULL_TYPE ? null : result;
}
@@ -28,6 +28,10 @@ import com.intellij.ui.JBColor;
import com.intellij.ui.SimpleTextAttributes;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.model.java.JavaModuleSourceRootTypes;
import org.jetbrains.jps.model.java.JavaResourceRootProperties;
import org.jetbrains.jps.model.java.JavaSourceRootProperties;
import org.jetbrains.jps.model.module.JpsModuleSourceRoot;
import org.jetbrains.jps.model.module.JpsModuleSourceRootType;
import javax.swing.*;
@@ -58,7 +62,7 @@ public class ContentEntryTreeCellRenderer extends NodeRenderer {
if (file != null && file.isDirectory()) {
final ContentEntry contentEntry = editor.getContentEntry();
if (contentEntry != null) {
final String prefix = getPrefix(contentEntry, file);
final String prefix = getPresentablePrefix(contentEntry, file);
if (!prefix.isEmpty()) {
append(" (" + prefix + ")", new SimpleTextAttributes(Font.PLAIN, JBColor.GRAY));
}
@@ -70,10 +74,14 @@ public class ContentEntryTreeCellRenderer extends NodeRenderer {
}
}
private static String getPrefix(final ContentEntry entry, final VirtualFile file) {
private static String getPresentablePrefix(final ContentEntry entry, final VirtualFile file) {
for (final SourceFolder sourceFolder : entry.getSourceFolders()) {
if (file.equals(sourceFolder.getFile())) {
return sourceFolder.getPackagePrefix();
JpsModuleSourceRoot element = sourceFolder.getJpsElement();
JavaSourceRootProperties properties = element.getProperties(JavaModuleSourceRootTypes.SOURCES);
if (properties != null) return properties.getPackagePrefix();
JavaResourceRootProperties resourceRootProperties = element.getProperties(JavaModuleSourceRootTypes.RESOURCES);
if (resourceRootProperties != null) return resourceRootProperties.getRelativeOutputPath();
}
}
return "";
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
* Copyright 2000-2015 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.
@@ -54,5 +54,9 @@ public class RegistryToggleAction extends ToggleAction {
@Override
public void setSelected(AnActionEvent e, boolean state) {
Registry.get(myKey).setValue(state);
doWhenDone(e);
}
public void doWhenDone(AnActionEvent e) {
}
}
@@ -180,7 +180,7 @@ class FilePointerPartNode {
for (FilePointerPartNode child : children) {
// find the right child (its part should start with ours)
int i = child.indexOfFirstDifferentChar(path, index);
if (i != index && (i > index+1 || path.charAt(index) != '/')) {
if (i != index && (i > index+1 || path.charAt(index) != '/' || index == 0)) {
FilePointerPartNode node = child.findPointerOrCreate(path, index, fileAndUrl);
if (node.leaf == null) pointersUnder++; // the new node's been created
return node;
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
* Copyright 2000-2015 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.
@@ -15,6 +15,7 @@
*/
package com.intellij.ui.mac;
import com.intellij.openapi.application.ApplicationNamesInfo;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.fileChooser.FileChooser;
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
@@ -37,6 +38,7 @@ import com.intellij.util.Consumer;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.UIUtil;
import com.sun.jna.Callback;
import com.sun.jna.Pointer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -59,53 +61,69 @@ public class MacFileChooserDialogImpl implements PathChooserDialog {
private final Project myProject;
private Consumer<List<VirtualFile>> myCallback;
private static boolean checkFile(@NotNull ID self, ID url, boolean checkDirectories) {
MacFileChooserDialogImpl dialog = ourImplMap.get(self);
if (dialog == null) {
// Since it has already been removed from the map, the file is likely to be valid if the user was able to select it
return true;
}
if (url == null || url.intValue() == 0) {
return false;
}
ID filename = Foundation.invoke(url, "path");
String fileName = Foundation.toStringViaUTF8(filename);
if (fileName == null) {
return false;
}
VirtualFile file = LocalFileSystem.getInstance().findFileByPath(fileName);
return file == null || (!checkDirectories && file.isDirectory()) || dialog.myChooserDescriptor.isFileSelectable(file);
}
private static final Callback SHOULD_ENABLE_CALLBACK = new Callback() {
@SuppressWarnings("UnusedDeclaration")
public boolean callback(ID self, String selector, ID panel, ID url) {
// allow any directory - ability to select nested directories
return checkFile(self, url, false);
try {
return checkFile(self, url, true); // allow any directory - ability to select nested directories
}
catch (Exception e) {
return false;
}
}
};
private static final Callback VALIDATE_URL_CALLBACK = new Callback() {
@SuppressWarnings("UnusedDeclaration")
public boolean callback(ID self, String selector, ID panel, ID url, ID outError) {
if (checkFile(self, url, true)) {
return true;
try {
return checkFile(self, url, false);
}
/*
if (!outError.equals(ID.NIL)) {
ID error = Foundation.invoke("NSError", "errorWithDomain:code:userInfo:", Foundation.nsString("org.jetbrains"),
Foundation.createDict(new String[]{"NSLocalizedDescriptionKey"}, new Object[]{"Not allowed"}));
// todo "*outError = error"
catch (Exception e) {
if (!ID.NIL.equals(outError)) {
ID domain = Foundation.nsString(ApplicationNamesInfo.getInstance().getProductName());
ID dict = Foundation.createDict(new String[]{"NSLocalizedDescription"}, new Object[]{e.getMessage()});
ID error = Foundation.invoke("NSError", "errorWithDomain:code:userInfo:", domain, 100, dict);
new Pointer(outError.longValue()).setLong(0, error.longValue());
}
return false;
}
*/
return false;
}
};
private static boolean checkFile(@NotNull ID self, ID url, boolean quickCheck) throws Exception {
MacFileChooserDialogImpl impl = ourImplMap.get(self);
if (impl == null) {
return true; // already removed from the map: the file is likely to be valid since the user was able to select it
}
if (url == null || url.intValue() == 0) {
return false;
}
ID filename = Foundation.invoke(url, "path");
String path = Foundation.toStringViaUTF8(filename);
if (path == null) {
return false;
}
VirtualFile file = LocalFileSystem.getInstance().findFileByPath(path);
if (file == null || quickCheck && file.isDirectory()) {
return true;
}
if (!impl.myChooserDescriptor.isFileSelectable(file)) {
return false;
}
if (!quickCheck) {
VirtualFile[] files = {file};
impl.myChooserDescriptor.validateSelectedFiles(files);
}
return true;
}
private static final Callback OPEN_PANEL_DID_END = new Callback() {
@SuppressWarnings("UnusedDeclaration")
public void callback(ID self, String selector, ID openPanelDidEnd, ID returnCode, ID contextInfo) {
@@ -300,7 +318,6 @@ public class MacFileChooserDialogImpl implements PathChooserDialog {
@Override
public void choose(@Nullable final VirtualFile toSelect, @NotNull final Consumer<List<VirtualFile>> callback) {
ExtensionsInitializer.initialize();
myCallback = callback;
@@ -356,12 +373,11 @@ public class MacFileChooserDialogImpl implements PathChooserDialog {
return Foundation.invoke(id, Foundation.createSelector(selector), args);
}
/** This class is intended to force extensions initialization on EDT thread (IDEA-107271)
*/
/** This class is intended to force extensions initialization on EDT thread (IDEA-107271) */
private static class ExtensionsInitializer {
private ExtensionsInitializer() {}
private static boolean initialized;
private static void initialize () {
private static void initialize() {
if (initialized) return;
UIUtil.invokeAndWaitIfNeeded(new Runnable() {
@Override
@@ -372,5 +388,4 @@ public class MacFileChooserDialogImpl implements PathChooserDialog {
initialized = true;
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2009 JetBrains s.r.o.
* Copyright 2000-2015 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.
@@ -47,10 +47,9 @@ public class Growl {
final ID applicationIcon = getApplicationIcon();
final ID defaultNotifications = Foundation.fillArray(myDefaultNotification);
final ID allNotifications = Foundation.fillArray(myAllNotifications);
final ID userDict = Foundation.createDict(new String[]{GROWL_APP_NAME, GROWL_APP_ICON, GROWL_DEFAULT_NOTIFICATIONS, GROWL_ALL_NOTIFICATIONS},
new Object[]{myProductName, applicationIcon, defaultNotifications, allNotifications});
final ID userDict = Foundation.createDict(
new String[]{GROWL_APP_NAME, GROWL_APP_ICON, GROWL_DEFAULT_NOTIFICATIONS, GROWL_ALL_NOTIFICATIONS},
new Object[]{myProductName, applicationIcon, defaultNotifications, allNotifications});
final ID center = invoke("NSDistributedNotificationCenter", "defaultCenter");
final Object notificationName = Foundation.nsString(GROWL_APPLICATION_REGISTRATION_NOTIFICATION);
invoke(center, "postNotificationName:object:userInfo:deliverImmediately:", notificationName, null, userDict, true);
@@ -59,12 +58,11 @@ public class Growl {
public void notifyGrowlOf(final String notification, final String title, final String description) {
final ID autoReleasePool = createAutoReleasePool();
final ID dict = Foundation.createDict(new String[]{
GROWL_NOTIFICATION_NAME, GROWL_NOTIFICATION_TITLE, GROWL_NOTIFICATION_DESCRIPTION, GROWL_APP_NAME},
new Object[]{notification, title, description, myProductName});
final ID dict = Foundation.createDict(
new String[]{GROWL_NOTIFICATION_NAME, GROWL_NOTIFICATION_TITLE, GROWL_NOTIFICATION_DESCRIPTION, GROWL_APP_NAME},
new Object[]{notification, title, description, myProductName});
final ID center = invoke("NSDistributedNotificationCenter", "defaultCenter");
final Object notificationName = Foundation.nsString(GROWL_NOTIFICATION);
invoke(center, "postNotificationName:object:userInfo:deliverImmediately:", notificationName, null, dict, true);
invoke(autoReleasePool, "release");
}
@@ -163,6 +163,17 @@ public class VirtualFilePointerTest extends PlatformLangTestCase {
assertSameElements(myVirtualFilePointerManager.getPointersUnder(b, "p2"), p2);
}
public void testUrlsHavingOnlyStartingSlashInCommonAndInvalidUrlBetweenThem() throws Exception {
VirtualFilePointer p1 = myVirtualFilePointerManager.create("file:///a/p1", disposable, null);
myVirtualFilePointerManager.create("file://invalid/path", disposable, null);
VirtualFilePointer p2 = myVirtualFilePointerManager.create("file:///b/p2", disposable, null);
final LightVirtualFile root = new LightVirtualFile("/");
LightVirtualFile a = createLightFile(root, "a");
LightVirtualFile b = createLightFile(root, "b");
assertSameElements(myVirtualFilePointerManager.getPointersUnder(a, "p1"), p1);
assertSameElements(myVirtualFilePointerManager.getPointersUnder(b, "p2"), p2);
}
@NotNull
private static LightVirtualFile createLightFile(final LightVirtualFile parent, final String name) {
return new LightVirtualFile(name) {
@@ -27,10 +27,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.model.JpsElement;
import org.jetbrains.jps.model.JpsElementFactory;
import org.jetbrains.jps.model.java.JavaModuleSourceRootTypes;
import org.jetbrains.jps.model.java.JavaResourceRootType;
import org.jetbrains.jps.model.java.JavaSourceRootProperties;
import org.jetbrains.jps.model.java.JavaSourceRootType;
import org.jetbrains.jps.model.java.*;
import org.jetbrains.jps.model.module.JpsModuleSourceRoot;
import org.jetbrains.jps.model.module.JpsModuleSourceRootType;
import org.jetbrains.jps.model.module.JpsTypedModuleSourceRoot;
@@ -78,7 +75,14 @@ public class SourceFolderImpl extends ContentFolderBaseImpl implements SourceFol
@Override
public String getPackagePrefix() {
JavaSourceRootProperties properties = getJavaProperties();
return properties != null ? properties.getPackagePrefix() : DEFAULT_PACKAGE_PREFIX;
if (properties != null) {
return properties.getPackagePrefix();
}
JavaResourceRootProperties resourceJavaProperties = getResourceJavaProperties();
if (resourceJavaProperties != null) {
return resourceJavaProperties.getRelativeOutputPath().replace('/', '.');
}
return DEFAULT_PACKAGE_PREFIX;
}
@Nullable
@@ -86,6 +90,11 @@ public class SourceFolderImpl extends ContentFolderBaseImpl implements SourceFol
return myJpsElement.getProperties(JavaModuleSourceRootTypes.SOURCES);
}
@Nullable
private JavaResourceRootProperties getResourceJavaProperties() {
return myJpsElement.getProperties(JavaModuleSourceRootTypes.RESOURCES);
}
@Override
public void setPackagePrefix(@NotNull String packagePrefix) {
JavaSourceRootProperties properties = getJavaProperties();
@@ -112,6 +121,12 @@ public class SourceFolderImpl extends ContentFolderBaseImpl implements SourceFol
return myJpsElement;
}
private boolean isForGeneratedSources() {
JavaSourceRootProperties properties = getJavaProperties();
JavaResourceRootProperties resourceJavaProperties = getResourceJavaProperties();
return properties != null && properties.isForGeneratedSources() || resourceJavaProperties != null && resourceJavaProperties.isForGeneratedSources();
}
@Override
public int compareTo(ContentFolderBaseImpl folder) {
if (!(folder instanceof SourceFolderImpl)) return -1;
@@ -124,12 +139,8 @@ public class SourceFolderImpl extends ContentFolderBaseImpl implements SourceFol
if (i!= 0) return i;
i = Boolean.valueOf(isTestSource()).compareTo(sourceFolder.isTestSource());
if (i != 0) return i;
JavaSourceRootProperties properties1 = getJavaProperties();
JavaSourceRootProperties properties2 = sourceFolder.getJavaProperties();
if (properties1 != null && properties2 != null) {
i = Boolean.valueOf(properties1.isForGeneratedSources()).compareTo(properties2.isForGeneratedSources());
if (i != 0) return i;
}
i = Boolean.valueOf(isForGeneratedSources()).compareTo(sourceFolder.isForGeneratedSources());
if (i != 0) return i;
//todo[nik] perhaps we should use LinkedSet instead of SortedSet and get rid of this method
return myJpsElement.getRootType().getClass().getName().compareTo(sourceFolder.getRootType().getClass().getName());
}
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
* Copyright 2000-2015 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.
@@ -15,7 +15,11 @@
*/
package com.intellij.xdebugger.impl.actions;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.RegistryToggleAction;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.project.Project;
/**
* @author Konstantin Bulenkov
@@ -24,4 +28,15 @@ public class UseInlineDebuggerAction extends RegistryToggleAction {
public UseInlineDebuggerAction() {
super("ide.debugger.inline");
}
@Override
public void doWhenDone(AnActionEvent e) {
Project project = e.getProject();
if (project != null) {
final Editor editor = FileEditorManager.getInstance(project).getSelectedTextEditor();
if (editor != null) {
editor.getComponent().repaint();
}
}
}
}
@@ -5,8 +5,8 @@ This module encapsulates Django semi-public API knowledge, and not very stable b
"""
from optparse import Option
import django
from django.apps import registry
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.core.management import ManagementUtility, get_commands, BaseCommand
__author__ = 'Ilya.Kazakevich'
@@ -21,7 +21,11 @@ def report_data(dumper):
"""
utility = ManagementUtility()
for command_name in get_commands().keys():
command = utility.fetch_command(command_name)
try:
command = utility.fetch_command(command_name)
except ImproperlyConfigured:
continue # TODO: Log somehow
assert isinstance(command, BaseCommand)
dumper.start_command(command_name=command_name,
command_help_text=str(command.usage("").replace("%prog", command_name)),
@@ -856,8 +856,11 @@ custom.type.mimic.name=Dynamic class based on {0}
# Values for command argument value validation
commandLine.validation.badCommand=Unknown command
commandLine.validation.argMissing=Required argument value is missing
commandLine.validation.optArgMissing=Option argument value is missing
commandLine.validation.argBadValue=Argument can't have this value
commandLine.validation.excessArg=Excess argument value
commandLine.validation.badOption=Bad option or option already set
commandLine.validation.noArgAllowed=No argument allowed for this option
# And for labels
commandLine.subText.key.complete=Use {0} to complete selected variant
commandLine.subText.key.suggestions=Use {0} to view available values
@@ -107,8 +107,8 @@ public final class WordWithPosition extends Range<Integer> {
WordWithPosition position = (WordWithPosition)o;
if (getFrom() != position.getFrom()) return false;
if (getTo() != position.getTo()) return false;
if (!getFrom().equals(position.getFrom())) return false;
if (!getTo().equals(position.getTo())) return false;
if (!myWord.equals(position.myWord)) return false;
return true;
@@ -1,83 +0,0 @@
/*
* Copyright 2000-2015 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.commandInterface.chunkDriverBasedPresenter;
import com.jetbrains.python.WordWithPosition;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Chunk and its info pair. Chunk may be null, while chunk info always present.
* @author Ilya.Kazakevich
*/
final class ChunkAndInfo implements Comparable<ChunkAndInfo> {
@Nullable
private final WordWithPosition myChunk;
@NotNull
private final ChunkInfo myChunkInfo;
ChunkAndInfo(@Nullable final WordWithPosition chunk, @NotNull final ChunkInfo chunkInfo) {
myChunk = chunk;
myChunkInfo = chunkInfo;
}
/**
* @return chunk (word). may be null
*/
@Nullable
WordWithPosition getChunk() {
return myChunk;
}
/**
* @return chunk info.
*/
@NotNull
ChunkInfo getChunkInfo() {
return myChunkInfo;
}
@Override
public int compareTo(@NotNull final ChunkAndInfo o) {
if (myChunk == null && o.myChunk == null) {
return 0;
}
if (myChunk == null) {
return 1;
}
if (o.myChunk == null) {
return -1;
}
return myChunk.getFrom().compareTo(o.myChunk.getFrom());
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ChunkAndInfo info = (ChunkAndInfo)o;
if (myChunk != null ? !myChunk.equals(info.myChunk) : info.myChunk != null) return false;
return true;
}
@Override
public int hashCode() {
return myChunk != null ? myChunk.hashCode() : 0;
}
}
@@ -1,247 +0,0 @@
/*
* Copyright 2000-2015 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.commandInterface.chunkDriverBasedPresenter;
import com.intellij.util.Range;
import com.jetbrains.python.WordWithPosition;
import com.jetbrains.python.commandInterface.CommandInterfacePresenterAdapter;
import com.jetbrains.python.commandInterface.CommandInterfaceView;
import com.jetbrains.python.suggestionList.SuggestionsBuilder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
// TODO: Test
/**
* Presenter that uses {@link com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.ChunkDriver} to parse pack of chunks
* to obtain {@link com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.ChunkInfo}.
* Each chunk may be paired with certain chunk info. Such info tells presenter whether this chunk has info, error, suggestions and so on.
* If caret situated far from chunks, then next neariest chunk should be found (see {@link #findNearestChunkAndInfo()}.
*
*
* @author Ilya.Kazakevich
* @see com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.ChunkInfo
*/
public final class ChunkDriverBasedPresenter extends CommandInterfacePresenterAdapter {
@NotNull
private final ChunkDriver myChunkDriver;
@NotNull
private final SortedSet<ChunkAndInfo> myChunkAndInfos = new TreeSet<ChunkAndInfo>();
@Nullable
private Runnable myExecutor;
public ChunkDriverBasedPresenter(@NotNull final CommandInterfaceView view,
@NotNull final ChunkDriver chunkDriver) {
super(view);
myChunkDriver = chunkDriver;
}
@Override
public void launch() {
super.launch();
reparseText(true);
}
@Override
public void textChanged() {
reparseText(false);
}
private void reparseText(final boolean skipSuggestions) {
final List<WordWithPosition> chunks = WordWithPosition.splitText(myView.getText());
final ParseInfo parseInfo = myChunkDriver.parse(chunks);
myExecutor = parseInfo.getExecutor();
final List<ChunkInfo> chunkInfos = parseInfo.getChunkInfo();
assert chunkInfos.size() >= chunks.size() : "Driver did not return enough chunks";
assert !chunkInfos.isEmpty() : "At least one chunk info should exist";
myChunkAndInfos.clear();
for (int i = 0; i < chunkInfos.size(); i++) {
final ChunkInfo chunkInfo = chunkInfos.get(i);
final WordWithPosition chunk = chunks.size() > i ? chunks.get(i) : null;
myChunkAndInfos.add(new ChunkAndInfo(chunk, chunkInfo));
}
// configure Errors And Balloons
final Collection<WordWithPosition> infoBalloons = new ArrayList<WordWithPosition>();
final Collection<WordWithPosition> errorBalloons = new ArrayList<WordWithPosition>();
for (final ChunkAndInfo chunkInfoPair : myChunkAndInfos) {
final ChunkInfo chunkInfo = chunkInfoPair.getChunkInfo();
Range<Integer> chunk = chunkInfoPair.getChunk();
if (chunk == null) {
// After the last!
chunk = CommandInterfaceView.AFTER_LAST_CHARACTER_RANGE;
}
final String error = chunkInfo.getError();
if (error != null) {
errorBalloons.add(new WordWithPosition(error, chunk));
}
final String info = chunkInfo.getInfoBalloon();
if (info != null) {
infoBalloons.add(new WordWithPosition(info, chunk));
}
}
myView.setInfoAndErrors(infoBalloons, errorBalloons);
if (!skipSuggestions) {
configureSuggestion(false);
}
// Configure subtexts
final List<Range<Integer>> placesWhereSuggestionAvailable = new ArrayList<Range<Integer>>();
for (final ChunkAndInfo chunkAndInfo : myChunkAndInfos) {
Range<Integer> chunk = chunkAndInfo.getChunk();
// If some place has suggestions, then add it
if (chunkAndInfo.getChunkInfo().getSuggestions() != null) {
if (chunk == null) {
// If there is no such chunk, that means we are after the last character, so use "special case" here
//noinspection ReuseOfLocalVariable
chunk = CommandInterfaceView.AFTER_LAST_CHARACTER_RANGE;
}
placesWhereSuggestionAvailable.add(chunk);
}
}
myView.configureSubTexts(parseInfo.getStatusText(), placesWhereSuggestionAvailable);
}
@Override
public void suggestionRequested() {
configureSuggestion(true); // Show or hide
}
/**
* Displays suggestions if needed.
*
* @param requestedExplicitly is suggesions where requested by user explicitly or not
*/
private void configureSuggestion(final boolean requestedExplicitly) {
myView.removeSuggestions();
final ChunkAndInfo chunkAndInfo = findNearestChunkAndInfo();
final ChunkInfo chunkInfo = chunkAndInfo.getChunkInfo();
final WordWithPosition chunk = chunkAndInfo.getChunk();
final SuggestionInfo suggestionInfo = chunkInfo.getSuggestions();
if (suggestionInfo == null || (!suggestionInfo.isShowSuggestionsAutomatically() && !requestedExplicitly)) {
return;
}
final List<String> suggestions = new ArrayList<String>(suggestionInfo.getSuggestions());
if (chunk != null && !requestedExplicitly) {
filterLeaveOnlyMatching(suggestions, chunk.getText());
}
// TODO: Place to add history
if (!suggestions.isEmpty()) {
// No need to display empty suggestions
myView
.displaySuggestions(new SuggestionsBuilder(suggestions), suggestionInfo.isShowAbsolute(), (chunk == null ? null : chunk.getText()));
}
}
/**
* Filters collection of suggestions leaving only those starts with certain text.
* @param suggestions list to filter
* @param textToMatch leave only parts that start with this param
*/
private static void filterLeaveOnlyMatching(@NotNull final Iterable<String> suggestions, @NotNull final String textToMatch) {
// TODO: use guava instead?
final Iterator<String> iterator = suggestions.iterator();
while (iterator.hasNext()) {
if (!iterator.next().startsWith(textToMatch)) {
iterator.remove();
}
}
}
/**
* Searches for the nearest chunk and info to use. It may or may not find chunk, but it should always provide some chunk info.
*
* @return nearest chunk info and, probably, chunk.
*/
@NotNull
private ChunkAndInfo findNearestChunkAndInfo() {
final int caretPosition = myView.getCaretPosition();
for (final ChunkAndInfo chunkAndInfo : myChunkAndInfos) {
final Range<Integer> range = chunkAndInfo.getChunk();
if (range != null && range.isWithin(caretPosition)) {
return chunkAndInfo;
}
if (range != null && range.getFrom() > caretPosition) {
return new ChunkAndInfo(null, chunkAndInfo.getChunkInfo());
}
}
return new ChunkAndInfo(null, myChunkAndInfos.last().getChunkInfo());
}
@Override
public void completionRequested(@Nullable final String valueFromSuggestionList) {
final ChunkAndInfo chunkAndInfo = findNearestChunkAndInfo();
final WordWithPosition chunk = chunkAndInfo.getChunk();
if (valueFromSuggestionList != null) {
// Just insert it
if (chunk != null) { // If caret is on the chunk itself
myView.replaceText(chunk.getFrom(), chunk.getTo(), valueFromSuggestionList);
}
else {
myView.insertTextAfterCaret(valueFromSuggestionList);
}
return;
}
//User did not provide text no insert, do our best to find one
final ChunkInfo chunkInfo = chunkAndInfo.getChunkInfo();
final SuggestionInfo suggestionInfo = chunkInfo.getSuggestions();
if (suggestionInfo == null) {
return; // No suggestion available for this chunk
}
final List<String> suggestions = new ArrayList<String>(suggestionInfo.getSuggestions());
if (chunk != null) {
filterLeaveOnlyMatching(suggestions, chunk.getText());
}
if (suggestions.size() == 1) {
// Exclusive!
if (chunk != null) {
myView.replaceText(chunk.getFrom(), chunk.getTo(), suggestions.get(0));
}
else {
myView.insertTextAfterCaret(suggestions.get(0));
}
}
}
@Override
public void executionRequested() {
if (myExecutor == null) {
// TODO: Display error somehow
}
else {
myExecutor.run();
}
}
}
@@ -1,81 +0,0 @@
/*
* Copyright 2000-2015 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.commandInterface.chunkDriverBasedPresenter;
import org.jetbrains.annotations.Nullable;
/**
* Information about certain place in text, provided by chunk driver.
*
* @author Ilya.Kazakevich
*/
public final class ChunkInfo {
@Nullable
private final String myInfoBalloon;
@Nullable
private final String myError;
@Nullable
private final SuggestionInfo mySuggestions;
public ChunkInfo(@Nullable final String infoBalloon,
@Nullable final String error) {
this(infoBalloon, error, null);
}
/**
*
* @param infoBalloon Info balloon to display when caret meets this place (null if display nothing)
* @param error Error balloon to display when caret meets this place and underline text as error (null if no error)
* @param suggestions list of suggestions available in this place (if any)
*/
public ChunkInfo(@Nullable final String infoBalloon,
@Nullable final String error,
@Nullable final SuggestionInfo suggestions) {
myInfoBalloon = infoBalloon;
myError = error;
mySuggestions = suggestions;
}
/**
*
* @return Info balloon to display when caret meets this place (null if display nothing)
*/
@Nullable
public String getInfoBalloon() {
return myInfoBalloon;
}
/**
*
* @return Error balloon to display when caret meets this place and underline text as error (null if no error)
*/
@Nullable
public String getError() {
return myError;
}
/**
*
* @return list of suggestions available in this place (if any)
*/
@Nullable
public SuggestionInfo getSuggestions() {
return mySuggestions;
}
}
@@ -1,88 +0,0 @@
/*
* Copyright 2000-2015 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.commandInterface.chunkDriverBasedPresenter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* Pack of {@link com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.ChunkInfo} for certain chunks and other parsing info.
*
* @author Ilya.Kazakevich
*/
public final class ParseInfo {
@Nullable
private final String myStatusText;
@NotNull
private final List<ChunkInfo> myChunkInfo = new ArrayList<ChunkInfo>();
@Nullable
private final Runnable myExecutor;
/**
* @param chunkInfo Chunk info should match chunks in 1-to-1 manner:
* If "chunk1 chunk2 chunk3" were provided, you then need to return list where first chunkInfo matches first chunk ets.
* And there also should be one more chunkInfo for tail.
* @param statusText Status text {@link com.jetbrains.python.commandInterface.CommandInterfaceView view} may display.
* @param executor Engine to process command-line execution
*/
public ParseInfo(@NotNull final Collection<ChunkInfo> chunkInfo,
@Nullable final String statusText,
@Nullable final Runnable executor) {
myStatusText = statusText;
myChunkInfo.addAll(chunkInfo);
myExecutor = executor;
}
/**
* Simple parse info with out of status text and executor
*
* @param chunkInfo Chunk info (See {@link #ParseInfo(java.util.Collection, String, Runnable)}
* @see #ParseInfo(java.util.Collection, String, Runnable)
*/
public ParseInfo(@NotNull final Collection<ChunkInfo> chunkInfo) {
this(chunkInfo, null, null);
}
/**
* @return Status text {@link com.jetbrains.python.commandInterface.CommandInterfaceView view} may display.
*/
@Nullable
String getStatusText() {
return myStatusText;
}
/**
* @return Engine to process command-line execution
*/
@Nullable
Runnable getExecutor() {
return myExecutor;
}
/**
* @return Chunk info should match chunks in 1-to-1 manner, and there also should be one more chunkInfo for tail (
* see {@link #ParseInfo(java.util.Collection, String, Runnable) ctor} manual)
*/
@NotNull
List<ChunkInfo> getChunkInfo() {
return Collections.unmodifiableList(myChunkInfo);
}
}
@@ -1,30 +0,0 @@
/*
* Copyright 2000-2015 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.
*/
/**
* {@link com.jetbrains.python.commandInterface.CommandInterfacePresenter} implementation based on ideas of <strong>chunk</strong>
* and {@link com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.ChunkDriver}.
* This presenter explodes command line into several parts or chunks.
* Chunks then passed to {@link com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.ChunkDriver driver} and it returns
* all information it has about each chunk. Presenter uses this information to display chunks correctly using view.
* To use this package, {@link com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.ChunkDriver} should be implemented.
*
* See {@link com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.ChunkDriverBasedPresenter} as entry point
*
*
* @author Ilya.Kazakevich
*/
package com.jetbrains.python.commandInterface.chunkDriverBasedPresenter;
@@ -16,7 +16,6 @@
package com.jetbrains.python.commandInterface.command;
import com.intellij.openapi.module.Module;
import com.jetbrains.python.commandLineParser.CommandLineParseResult;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -59,7 +58,7 @@ public interface Command {
* Execute command
*
* @param module module to execute command against
* @param commandLine command's command line
* @param parameters command's arguments and options (just like entered by user but splitted by space)
*/
void execute(@NotNull final Module module, @NotNull final CommandLineParseResult commandLine);
void execute(@NotNull final Module module, @NotNull final List<String> parameters);
}
@@ -26,7 +26,9 @@ import java.util.Collections;
import java.util.List;
/**
* Command option
* Command option.
* It may have some long names (like --foo) and short (like -f), help text and arguments (if not flag option)
*
* @author Ilya.Kazakevich
*/
public final class Option {
@@ -40,17 +42,17 @@ public final class Option {
private final String myHelp;
/**
*
* @param argumentAndQuantity if option accepts argument, there should be pair of [argument_quantity, its_type_info]
* @param help option help
* @param shortNames option short names
* @param longNames option long names
* @param help option help
* @param shortNames option short names
* @param longNames option long names
*/
public Option(@Nullable final Pair<Integer, OptionArgumentInfo> argumentAndQuantity,
@NotNull final String help,
@NotNull final Collection<String> shortNames,
@NotNull final Collection<String> longNames) {
Preconditions.checkArgument(argumentAndQuantity == null || argumentAndQuantity.first > 0, "Illegal args and quantity: " + argumentAndQuantity);
Preconditions
.checkArgument(argumentAndQuantity == null || argumentAndQuantity.first > 0, "Illegal args and quantity: " + argumentAndQuantity);
myArgumentAndQuantity = argumentAndQuantity;
myShortNames.addAll(shortNames);
myLongNames.addAll(longNames);
@@ -65,6 +67,16 @@ public final class Option {
return Collections.unmodifiableList(myLongNames);
}
/**
* @return all option names (long and short)
*/
@NotNull
public List<String> getAllNames() {
final List<String> result = new ArrayList<String>(myLongNames);
result.addAll(myShortNames);
return result;
}
/**
* @return Option short names
*/
@@ -74,8 +86,7 @@ public final class Option {
}
/**
*
* @return if option accepts argument -- pair of [argument_quantity, its_type_info]. Null otherwise.
* @return if option accepts argument -- pair of [argument_quantity, its_type_info]. Null otherwise.
*/
@Nullable
public Pair<Integer, OptionArgumentInfo> getArgumentAndQuantity() {
@@ -24,7 +24,7 @@ import java.util.List;
* For options, whose argument is based on certain type.
*
* @author Ilya.Kazakevich
* @see com.jetbrains.python.commandInterface.command.OptionArgumentType
* @see OptionArgumentType
*/
public final class OptionTypedArgumentInfo implements OptionArgumentInfo {
@NotNull
@@ -42,7 +42,7 @@ public final class OptionTypedArgumentInfo implements OptionArgumentInfo {
// We only check integer for now
if (myType == OptionArgumentType.INTEGER) {
try {
// We just parse it to get exception
// We just getCommandLineInfo it to get exception
//noinspection ResultOfMethodCallIgnored
Integer.parseInt(value);
}
@@ -1,190 +0,0 @@
/*
* Copyright 2000-2015 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.commandInterface.commandBasedChunkDriver;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.util.Pair;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.WordWithPosition;
import com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.ChunkDriver;
import com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.ChunkInfo;
import com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.ParseInfo;
import com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.SuggestionInfo;
import com.jetbrains.python.commandInterface.command.Argument;
import com.jetbrains.python.commandInterface.command.ArgumentsInfo;
import com.jetbrains.python.commandInterface.command.Command;
import com.jetbrains.python.commandLineParser.CommandLineParseResult;
import com.jetbrains.python.commandLineParser.CommandLineParser;
import com.jetbrains.python.commandLineParser.CommandLinePartType;
import com.jetbrains.python.commandLineParser.MalformedCommandLineException;
import org.jetbrains.annotations.NotNull;
import java.util.*;
/**
* Chunk driver that uses pack of commands.
*
* @author Ilya.Kazakevich
*/
public final class CommandBasedChunkDriver implements ChunkDriver {
@NotNull
private final CommandLineParser myCommandLineParser;
@NotNull
private final Map<String, Command> myCommands = new TreeMap<String, Command>(); // To sort commands by name
@NotNull
private final Module myModule;
/**
* @param commandLineParser parser to use
* @param module module parsing takes place in
* @param commands available commands
*/
public CommandBasedChunkDriver(@NotNull final CommandLineParser commandLineParser,
@NotNull final Module module,
@NotNull final Collection<? extends Command> commands) {
myCommandLineParser = commandLineParser;
for (final Command command : commands) {
myCommands.put(command.getName(), command);
}
myModule = module;
}
@Override
@NotNull
public ParseInfo parse(@NotNull final List<WordWithPosition> chunks) {
// TODO: Refactor to add command first to prevent copy/paste
if (chunks.isEmpty()) {
return createBadCommandInfo(chunks.size());
}
try {
final CommandLineParseResult commandLine = myCommandLineParser.parse(chunks);
final Command command = myCommands.get(commandLine.getCommand().getText());
if (command == null) {
// Bad command inserted
return createBadCommandInfo(chunks.size());
}
// Command exists, lets check its arguments
// TODO: Support options as well
// First, validate values
final ArgumentsInfo commandArgumentsInfo = command.getArgumentsInfo();
final List<ChunkInfo> chunkInfo = new ArrayList<ChunkInfo>();
// First chunk iscommand and it seems to be ok
chunkInfo.add(new ChunkInfo(null, null, new SuggestionInfo(false, true, myCommands.keySet())));
// Now add balloons, info and suggestions
for (int i = 0; i < commandLine.getParts().size(); i++) {
final Pair<Boolean, Argument> argumentPair = commandArgumentsInfo.getArgument(i);
if (argumentPair == null) { // Excess argument!
chunkInfo.add(new ChunkInfo(null, PyBundle.message("commandLine.validation.excessArg")));
continue;
}
final Argument argument = argumentPair.getSecond();
final List<String> availableValues = argument.getAvailableValues();
final Pair<CommandLinePartType, WordWithPosition> part = commandLine.getParts().get(i);
if (part.first != CommandLinePartType.ARGUMENT) {
// Only arguments are supported now, so we have nothing to say about this chunk
chunkInfo.add(new ChunkInfo(null, null));
}
final String argumentValue = part.second.getText();
String errorMessage = null;
if (availableValues != null && !availableValues.contains(argumentValue)) {
// Bad value
errorMessage = PyBundle.message("commandLine.validation.argBadValue");
}
// Argument seems to be ok. We suggest values automatically only if value is bad
chunkInfo.add(new ChunkInfo(argument.getHelpText(), errorMessage,
(availableValues != null ? new SuggestionInfo(errorMessage != null, false, availableValues) : null)));
}
final Pair<Boolean, Argument> nextArgumentPair = commandArgumentsInfo.getArgument(commandLine.getParts().size());
if (nextArgumentPair != null) {
// Next arg exists
final Argument nextArgument = nextArgumentPair.getSecond();
final List<String> availableValues = nextArgument.getAvailableValues();
// Only add error if required
final String error = nextArgumentPair.first ? PyBundle.message("commandLine.validation.argMissing") : null;
final ChunkInfo lastArgInfo =
new ChunkInfo(nextArgument.getHelpText(), error,
(availableValues != null ? new SuggestionInfo(false, false, availableValues) : null));
chunkInfo.add(lastArgInfo);
}
else {
// Looks like all arguments are satisfied. Adding empty chunk to prevent completion etc.
// This is a hack, but with out of it last chunkinfo will always be used, even 200 chars after last place
chunkInfo.add(new ChunkInfo(null, null));
}
assert chunkInfo.size() >= chunks.size() : "Contract broken: not enough chunks";
return new ParseInfo(chunkInfo, command.getHelp(), new MyExecutor(command, commandLine));
}
catch (final MalformedCommandLineException ignored) {
// Junk enetered!
return createBadCommandInfo(chunks.size());
}
}
/**
* Creates parse info signaling command is bad or junk
*
* @param numberOfChunks number of chunks provided by user (we must return chunk info for each chunk + 1, accroding to contract)
* @return parse info to return
*/
@NotNull
private ParseInfo createBadCommandInfo(final int numberOfChunks) {
final List<ChunkInfo> result = new ArrayList<ChunkInfo>();
// We know that first chunk command line, but we can't say anything about outher chunks except they are bad.
// How ever, we must say something according to contract (number of infos should be equal or greater than number of chunks)
result
.add(new ChunkInfo(null, PyBundle.message("commandLine.validation.badCommand"), new SuggestionInfo(true, true, myCommands.keySet())));
for (int i = 1; i < numberOfChunks; i++) {
result.add(
new ChunkInfo(null, PyBundle.message("commandLine.validation.badCommand")));
}
return new ParseInfo(result);
}
/**
* Adapter that executes command using {@link Command#execute(com.intellij.openapi.module.Module, com.jetbrains.python.commandLineParser.CommandLineParseResult)}
*/
private class MyExecutor implements Runnable {
@NotNull
private final Command myCommand;
@NotNull
private final CommandLineParseResult myCommandLine;
MyExecutor(@NotNull final Command command, @NotNull final CommandLineParseResult line) {
myCommand = command;
myCommandLine = line;
}
@Override
public void run() {
myCommand.execute(myModule, myCommandLine);
}
}
}
@@ -0,0 +1,204 @@
/*
* Copyright 2000-2015 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.commandInterface.commandBasedRangeDriver;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.Range;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.commandInterface.command.Argument;
import com.jetbrains.python.commandInterface.command.Command;
import com.jetbrains.python.commandInterface.command.Option;
import com.jetbrains.python.commandInterface.command.OptionArgumentInfo;
import com.jetbrains.python.commandInterface.rangeBasedPresenter.Executor;
import com.jetbrains.python.commandInterface.rangeBasedPresenter.RangeInfo;
import com.jetbrains.python.commandInterface.rangeBasedPresenter.RangeInfoDriver;
import com.jetbrains.python.commandInterface.rangeBasedPresenter.SuggestionInfo;
import com.jetbrains.python.commandLineParser.CommandLine;
import com.jetbrains.python.commandLineParser.CommandLineParser;
import com.jetbrains.python.commandLineParser.CommandLinePart;
import com.jetbrains.python.commandLineParser.MalformedCommandLineException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* Driver that returns pack of range infos for certain command line
*
* @author Ilya.Kazakevich
*/
public final class CommandBasedRangeInfoDriver implements RangeInfoDriver {
@NotNull
private final Map<String, Command> myCommands = new TreeMap<String, Command>(); // To sort commands by name
@NotNull
private final Module myModule;
@NotNull
private final CommandLineParser myCommandLineParser;
/**
*
* @param module module (to be used in execution)
* @param commandLineParser parser to parse command lines
* @param commands available commands
*/
public CommandBasedRangeInfoDriver(
@NotNull final Module module,
@NotNull final CommandLineParser commandLineParser,
@NotNull final Collection<? extends Command> commands) {
for (final Command command : commands) {
myCommands.put(command.getName(), command);
}
myModule = module;
myCommandLineParser = commandLineParser;
}
@NotNull
@Override
public Pair<Executor, List<RangeInfo>> getCommandLineInfo(@NotNull final String commandLineText) {
// TODO: Copty/paste with exceptiojn
if (StringUtil.isEmpty(commandLineText)) {
final RangeInfo info = new RangeInfo(null, "", new SuggestionInfo(
true, true, myCommands.keySet()
), RangeInfo.TERMINATION_RANGE, false
);
return new Pair<Executor, List<RangeInfo>>(null, Collections.singletonList(info));
}
final CommandLine commandLine;
try {
commandLine = myCommandLineParser.parse(commandLineText);
}
catch (final MalformedCommandLineException ignored) {
return new Pair<Executor, List<RangeInfo>>(null, Collections.singletonList(
new RangeInfo(null, PyBundle.message("commandLine.validation.badCommand"), new SuggestionInfo(
false, true, myCommands.keySet()
), new Range<Integer>(0, commandLineText.length()), false)
));
}
final Command command = getExistingCommand(commandLine);
final List<CommandLinePart> commandLineParts = commandLine.getParts();
if (command == null) {
// Bad command inserted
return createBadCommandInfo(commandLine);
}
final UnusedOptionsCollector unusedOptionsCollector = UnusedOptionsCollector.create(command, commandLineParts);
final RangeInfoCollector rangeInfoCollector = RangeInfoCollector.create(command, commandLineParts, unusedOptionsCollector);
final SuggestionInfo commandSuggestions = new SuggestionInfo(false, true, myCommands.keySet());
// Add command as first range info
final List<RangeInfo> rangeInfos =
new ArrayList<RangeInfo>(Collections.singletonList(new RangeInfo(null, null, commandSuggestions, commandLine.getCommand(), false)));
// Then add collected infos
rangeInfos.addAll(rangeInfoCollector.getRangeInfos());
/////// What about "after the caret" ?
final Pair<Boolean, Argument> unsatisfiedArgument = rangeInfoCollector.getUnsatisfiedPositionalArgument();
final OptionArgumentInfo unsatisfiedOptionArgument = rangeInfoCollector.getUnsatisfiedOptionArgument();
// TODO: Move to collector after test
if (unsatisfiedOptionArgument != null) {
final List<String> availableValues = unsatisfiedOptionArgument.getAvailableValues();
final SuggestionInfo suggestionInfo;
if (availableValues != null) {
suggestionInfo =
new SuggestionInfo(false, false, availableValues);
}
else {
suggestionInfo = null;
}
rangeInfos
.add(new RangeInfo(null, PyBundle.message("commandLine.validation.optArgMissing"), suggestionInfo, RangeInfo.TERMINATION_RANGE,
false));
}
else if (unsatisfiedArgument != null) {
final boolean required = unsatisfiedArgument.first;
final Argument argument = unsatisfiedArgument.second;
// Only add error if required
final String error = required ? PyBundle.message("commandLine.validation.argMissing") : null;
final List<String> availableValues = unusedOptionsCollector.addUnusedOptions(argument.getAvailableValues());
final RangeInfo lastArgInfo =
new RangeInfo(argument.getHelpText(), error,
(availableValues != null ? new SuggestionInfo(false, false, availableValues) : null), RangeInfo.TERMINATION_RANGE,
false);
rangeInfos.add(lastArgInfo);
}
else {
// Looks like all arguments are satisfied. Adding empty chunk to prevent completion etc.
// This is a hack, but with out of it last range info will always be used, even 200 chars after last place
rangeInfos.add(new RangeInfo(null, null, rangeInfoCollector.getCurrentSuggestions(false, null), RangeInfo.TERMINATION_RANGE, false));
}
assert rangeInfos.size() >= commandLineParts.size() : "Contract broken: not enough chunks";
return Pair.<Executor, List<RangeInfo>>create(new CommandExecutor(command, myModule, commandLine.getPartsAsText()), rangeInfos);
}
@Nullable
private Command getExistingCommand(@NotNull final CommandLine commandLine) {
return myCommands.get(commandLine.getCommand().getText());
}
/**
* Creates range info info signaling command is bad or junk
*
*
* @param commandLine command line passed by user
* @return info to return
*/
@NotNull
private Pair<Executor, List<RangeInfo>> createBadCommandInfo(final CommandLine commandLine) {
final List<RangeInfo> result = new ArrayList<RangeInfo>();
// We know that first chunk command line, but we can't say anything about outher chunks except they are bad.
// How ever, we must say something
result
.add(new RangeInfo(null, PyBundle.message("commandLine.validation.badCommand"), new SuggestionInfo(true, true, myCommands.keySet()),
commandLine.getCommand(), false));
// Command is unknown, all other parts are junk
for (final CommandLinePart part : commandLine.getParts()) {
result.add(new RangeInfo(null, "", false, part.getWord()));
}
return Pair.create(null, result);
}
/**
* Finds option by its name
* @param command current command
* @param optionName option name
* @return option or null if no option found
*/
@Nullable
static Option findOptionByName(@NotNull final Command command, @NotNull final String optionName) {
for (final Option option : command.getOptions()) {
for (final String name : option.getAllNames()) {
if (name.equals(optionName)) {
return option;
}
}
}
return null;
}
}
@@ -0,0 +1,62 @@
/*
* Copyright 2000-2015 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.commandInterface.commandBasedRangeDriver;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.ArrayUtil;
import com.jetbrains.python.commandInterface.command.Command;
import com.jetbrains.python.commandInterface.rangeBasedPresenter.Executor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
/**
* Executes commands according to {@link Executor} contract
*
* @author Ilya.Kazakevich
*/
final class CommandExecutor implements Executor {
@NotNull
private final Command myCommand;
@NotNull
private final Module myModule;
@NotNull
private final String[] myArguments;
/**
* @param command command to execute
* @param module module to execute against
* @param argumentsLine all command arguments as testline
*/
CommandExecutor(@NotNull final Command command, @NotNull final Module module, @NotNull final String argumentsLine) {
myCommand = command;
myModule = module;
myArguments = (StringUtil.isEmpty(argumentsLine) ? ArrayUtil.EMPTY_STRING_ARRAY : argumentsLine.split(" "));
}
@Nullable
@Override
public String getExecutionDescription() {
return myCommand.getHelp();
}
@Override
public void execute() {
myCommand.execute(myModule, Arrays.asList(myArguments));
}
}
@@ -0,0 +1,250 @@
/*
* Copyright 2000-2015 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.commandInterface.commandBasedRangeDriver;
import com.google.common.collect.Sets;
import com.intellij.openapi.util.Pair;
import com.intellij.util.containers.HashSet;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.WordWithPosition;
import com.jetbrains.python.commandInterface.command.Argument;
import com.jetbrains.python.commandInterface.command.Command;
import com.jetbrains.python.commandInterface.command.Option;
import com.jetbrains.python.commandInterface.command.OptionArgumentInfo;
import com.jetbrains.python.commandInterface.rangeBasedPresenter.RangeInfo;
import com.jetbrains.python.commandInterface.rangeBasedPresenter.SuggestionInfo;
import com.jetbrains.python.commandLineParser.CommandLineArgument;
import com.jetbrains.python.commandLineParser.CommandLineOption;
import com.jetbrains.python.commandLineParser.CommandLinePart;
import com.jetbrains.python.commandLineParser.CommandLinePartVisitor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* Visitor that collections ranges info by visiting options and arguments
*
* @author Ilya.Kazakevich
*/
final class RangeInfoCollector implements CommandLinePartVisitor {
/**
* Options already met in command line. It has nothing to do with {@link UnusedOptionsCollector}!
*/
@NotNull
private final Set<String> myUsedOptions = new HashSet<String>();
@NotNull
private final List<RangeInfo> myRangeInfos = new ArrayList<RangeInfo>();
@NotNull
private final Command myCommand;
private int myNumOfProcessedPositionalArguments = 0;
private boolean mySkipNextArgument;
private Pair<Integer, OptionArgumentInfo> myExpectedOptionArgument;
@NotNull
private final UnusedOptionsCollector myUnusedOptionsCollector;
/**
* @param command command
* @param unusedOptionsCollector instance of unused options collector
*/
private RangeInfoCollector(@NotNull final Command command,
@NotNull final UnusedOptionsCollector unusedOptionsCollector) {
myCommand = command;
myUnusedOptionsCollector = unusedOptionsCollector;
}
@NotNull
static RangeInfoCollector create(@NotNull final Command command,
@NotNull final Iterable<CommandLinePart> commandLineParts,
@NotNull final UnusedOptionsCollector unusedOptionsCollector) {
final RangeInfoCollector infoCollector = new RangeInfoCollector(command, unusedOptionsCollector);
for (final CommandLinePart part : commandLineParts) {
part.accept(infoCollector);
}
return infoCollector;
}
@Override
public void visitOption(@NotNull final CommandLineOption option) {
if (processOptionArgument(option.getWord())) {
return;
}
final Option commandOption = CommandBasedRangeInfoDriver.findOptionByName(myCommand, option.getOptionName());
final WordWithPosition attachedArgument = option.getAttachedArgument();
if (commandOption == null) {
// There is no such option
// To Mark attached arg so we skip it in {@link #visitArgument}
if (attachedArgument != null) {
mySkipNextArgument = true;
}
// No such option
myRangeInfos.add(new RangeInfo(null, PyBundle.message("commandLine.validation.badOption"), getCurrentSuggestions(false, null),
option.getWord(), false));
return;
}
final Pair<Integer, OptionArgumentInfo> argumentAndQuantity = commandOption.getArgumentAndQuantity();
// If option already used, then mark it
final String optionAlreadyUsed = Sets.intersection(Sets.newHashSet(commandOption.getAllNames()), myUsedOptions).isEmpty()
? null
: PyBundle.message("commandLine.validation.badOption");
myRangeInfos
.add(new RangeInfo(commandOption.getHelp(), optionAlreadyUsed, getCurrentSuggestions(false, null), option.getWord(),
argumentAndQuantity != null));
// remove from existing options
myUsedOptions.addAll(commandOption.getAllNames());
if (argumentAndQuantity == null) {
if (attachedArgument != null) {
myRangeInfos.add(new RangeInfo(null, PyBundle.message("commandLine.validation.noArgAllowed"), false, attachedArgument));
mySkipNextArgument = true;
}
}
else {
// Some option args required
myExpectedOptionArgument = argumentAndQuantity;
}
}
/**
* Process option argument (not to be confused with positional!)
*
* @param currentPart part with argument.
* @return false if there should not be any option argument so this method did nothing. True if there should be and it is processed.
*/
private boolean processOptionArgument(@NotNull final WordWithPosition currentPart) {
if (myExpectedOptionArgument == null) {
return false;
}
final OptionArgumentInfo argumentInfo = myExpectedOptionArgument.second;
int argsLeft = myExpectedOptionArgument.first;
final boolean valid = argumentInfo.isValid(currentPart.getText());
final List<String> availableValues = argumentInfo.getAvailableValues();
final SuggestionInfo suggestions;
if (availableValues != null) {
suggestions = new SuggestionInfo(false, false, availableValues);
}
else {
suggestions = null;
}
myRangeInfos.add(new RangeInfo(null, (valid ? null : PyBundle.message("commandLine.validation.argBadValue")),
suggestions, currentPart, false));
if (--argsLeft == 0) {
myExpectedOptionArgument = null;
}
else {
myExpectedOptionArgument = Pair.create(argsLeft, argumentInfo);
}
return true;
}
/**
* Returns suggestions available for current position.
*
* @param showAutomatically make suggestions displayed automatically
* @param argumentPair argument to use. If null, current argument according to internal counter would be used.
* @return suggestiom info or null if no suggestion available
*/
@Nullable
SuggestionInfo getCurrentSuggestions(final boolean showAutomatically, @Nullable Pair<Boolean, Argument> argumentPair) {
if (argumentPair == null) {
argumentPair = myCommand.getArgumentsInfo().getArgument(myNumOfProcessedPositionalArguments);
}
final List<String> suggestions = argumentPair != null ? myUnusedOptionsCollector.addUnusedOptions(
argumentPair.second.getAvailableValues()) : myUnusedOptionsCollector.addUnusedOptions(
null);
if (suggestions == null) {
return null;
}
return new SuggestionInfo(showAutomatically, false, suggestions);
}
@Override
public void visitArgument(@NotNull final CommandLineArgument argument) {
if (mySkipNextArgument) {
// Skip argument, clear flag and do nothing
mySkipNextArgument = false;
return;
}
if (processOptionArgument(argument.getWord())) {
return;
}
final Pair<Boolean, Argument> argumentPair = myCommand.getArgumentsInfo().getArgument(myNumOfProcessedPositionalArguments++);
if (argumentPair == null) {
//Exceed!
myRangeInfos.add(new RangeInfo(null, PyBundle.message("commandLine.validation.excessArg"), false, argument.getWord()));
return;
}
final Argument commandArgument = argumentPair.second;
final List<String> argumentAvailableValues = commandArgument.getAvailableValues();
final String argumentValue = argument.getWord().getText();
String errorMessage = null;
if (argumentAvailableValues != null && !argumentAvailableValues.contains(argumentValue)) {
// Bad value
errorMessage = PyBundle.message("commandLine.validation.argBadValue");
}
// Argument seems to be ok. We suggest values automatically only if value is bad
myRangeInfos.add(new RangeInfo(commandArgument.getHelpText(), errorMessage,
getCurrentSuggestions(errorMessage != null, argumentPair),
argument.getWord(), false));
}
/**
* @return calculates range infos for all command line parts (not the command itself!)
*/
@NotNull
Collection<RangeInfo> getRangeInfos() {
return Collections.unmodifiableList(myRangeInfos);
}
/**
* @return Unsatisfied (currently expected) positional argument ([required, arg]) or null if no arg expected
*/
@Nullable
Pair<Boolean, Argument> getUnsatisfiedPositionalArgument() {
return myCommand.getArgumentsInfo().getArgument(myNumOfProcessedPositionalArguments);
}
/**
* @return Unsatisfied (currently expected) option argument or null if no option argument expected
*/
public OptionArgumentInfo getUnsatisfiedOptionArgument() {
if (myExpectedOptionArgument != null) {
return myExpectedOptionArgument.second;
}
else {
return null;
}
}
}
@@ -0,0 +1,97 @@
/*
* Copyright 2000-2015 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.commandInterface.commandBasedRangeDriver;
import com.intellij.util.containers.HashSet;
import com.jetbrains.python.commandInterface.command.Command;
import com.jetbrains.python.commandInterface.command.Option;
import com.jetbrains.python.commandLineParser.CommandLineArgument;
import com.jetbrains.python.commandLineParser.CommandLineOption;
import com.jetbrains.python.commandLineParser.CommandLinePart;
import com.jetbrains.python.commandLineParser.CommandLinePartVisitor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
/**
* Collections all used and unused options to report unused options then.
* It may be used to add unused options to suggestion list {@link #addUnusedOptions(Collection)}
*
* @author Ilya.Kazakevich
*/
final class UnusedOptionsCollector implements CommandLinePartVisitor {
@NotNull
private final Set<String> myUnusedOptions = new HashSet<String>();
@NotNull
private final Command myCommand;
/**
* @param command command to be used
* @param commandLineParts command line parts
* @return instance
*/
static UnusedOptionsCollector create(@NotNull final Command command, @NotNull final Iterable<CommandLinePart> commandLineParts) {
final UnusedOptionsCollector collector = new UnusedOptionsCollector(command);
for (final CommandLinePart part : commandLineParts) {
part.accept(collector);
}
return collector;
}
private UnusedOptionsCollector(@NotNull final Command command) {
for (final Option option : command.getOptions()) {
myUnusedOptions.addAll(option.getAllNames());
}
myCommand = command;
}
@Override
public void visitOption(@NotNull final CommandLineOption option) {
final Option commandOption = CommandBasedRangeInfoDriver.findOptionByName(myCommand, option.getOptionName());
if (commandOption != null) {
myUnusedOptions.removeAll(commandOption.getAllNames());
}
}
/**
* Merges list of unused options and other values provided as argument.
*
* @param mainValues values to merge options with. May be null.
* @return null of no options and no values provided or merged list of values and options. See method usages for more info
*/
@Nullable
List<String> addUnusedOptions(@Nullable final Collection<String> mainValues) {
if (mainValues == null && myUnusedOptions.isEmpty()) {
return null;
}
final List<String> result = new ArrayList<String>(myUnusedOptions);
if (mainValues != null) {
result.addAll(mainValues);
}
return (result.isEmpty() ? null : result);
}
@Override
public void visitArgument(@NotNull final CommandLineArgument argument) {
}
}
@@ -15,10 +15,10 @@
*/
/**
* {@link com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.ChunkDriver} implementation based on idea of
* {@link com.jetbrains.python.commandInterface.rangeBasedPresenter.RangeInfoDriver} implementation based on idea of
* {@link com.jetbrains.python.commandInterface.command command, option and argument}.
*
* See {@link com.jetbrains.python.commandInterface.commandBasedChunkDriver.CommandBasedChunkDriver} as entry point.
* See {@link com.jetbrains.python.commandInterface.commandBasedRangeDriver.CommandBasedRangeInfoDriver} as entry point.
* It parses command line using {@link com.jetbrains.python.commandLineParser.CommandLineParser} and finds matching command and arguments
* provided by user
*
@@ -26,4 +26,4 @@
* @see com.jetbrains.python.commandInterface.command.Command
* @author Ilya.Kazakevich
*/
package com.jetbrains.python.commandInterface.commandBasedChunkDriver;
package com.jetbrains.python.commandInterface.commandBasedRangeDriver;
@@ -31,7 +31,7 @@
*
* <p>
* There is also swing-based view implementation in {@link com.jetbrains.python.commandInterface.swingView}
* and presenter implementation based on idea of commands with arguments. See {@link com.jetbrains.python.commandInterface.chunkDriverBasedPresenter}
* and presenter implementation based on idea of commands with arguments. See {@link com.jetbrains.python.commandInterface.rangeBasedPresenter}
* </p>
*
* <p>
@@ -0,0 +1,36 @@
/*
* Copyright 2000-2015 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.commandInterface.rangeBasedPresenter;
import org.jetbrains.annotations.Nullable;
/**
* Engine to execute command line
*
* @author Ilya.Kazakevich
*/
public interface Executor {
/**
* @return information about what to execute (like command name)
*/
@Nullable
String getExecutionDescription();
/**
* Execute command!
*/
void execute();
}
@@ -0,0 +1,246 @@
/*
* Copyright 2000-2015 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.commandInterface.rangeBasedPresenter;
import com.intellij.openapi.util.Pair;
import com.intellij.util.Range;
import com.jetbrains.python.WordWithPosition;
import com.jetbrains.python.commandInterface.CommandInterfacePresenterAdapter;
import com.jetbrains.python.commandInterface.CommandInterfaceView;
import com.jetbrains.python.suggestionList.SuggestionsBuilder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* Presenter that uses {@link RangeInfoDriver} to obtain {@link RangeInfo info}.
* Such info tells presenter whether this text has info, error, suggestions and so on.
* If caret situated far from text, then next neariest range should be found (see {@link #findNearestRangeInfo()}.
*
* @author Ilya.Kazakevich
* @see RangeInfo
*/
public final class RangeBasedPresenter extends CommandInterfacePresenterAdapter {
@NotNull
private final RangeInfoDriver myRangeInfoDriver;
@Nullable
private Executor myExecutor;
private final SortedSet<RangeInfo> myRangeInfos = new TreeSet<RangeInfo>();
public RangeBasedPresenter(@NotNull final CommandInterfaceView view,
@NotNull final RangeInfoDriver rangeInfoDriver) {
super(view);
myRangeInfoDriver = rangeInfoDriver;
}
@Override
public void launch() {
super.launch();
reparseText(true);
}
@Override
public void textChanged() {
reparseText(false);
}
private void reparseText(final boolean skipSuggestions) {
final Pair<Executor, List<RangeInfo>> rangeInfoStructure = myRangeInfoDriver.getCommandLineInfo(myView.getText());
myExecutor = rangeInfoStructure.first;
final List<RangeInfo> rangeInfos = rangeInfoStructure.second;
assert !rangeInfos.isEmpty() : "At least one chunk info should exist";
myRangeInfos.clear();
myRangeInfos.addAll(rangeInfos);
// configure Errors And Balloons
final Collection<WordWithPosition> infoBalloons = new ArrayList<WordWithPosition>();
final Collection<WordWithPosition> errorBalloons = new ArrayList<WordWithPosition>();
for (final RangeInfo rangeInfo : rangeInfos) {
final String error = rangeInfo.getError();
if (error != null) {
errorBalloons.add(new WordWithPosition(error, rangeInfo));
}
final String info = rangeInfo.getInfoBalloon();
if (info != null) {
infoBalloons.add(new WordWithPosition(info, rangeInfo));
}
}
myView.setInfoAndErrors(infoBalloons, errorBalloons);
if (!skipSuggestions) {
configureSuggestion(false);
}
// Configure subtexts
final List<Range<Integer>> placesWhereSuggestionAvailable = new ArrayList<Range<Integer>>();
for (final RangeInfo rangeInfo : rangeInfos) {
// If some place has suggestions, then add it
if (rangeInfo.getSuggestions() != null) {
placesWhereSuggestionAvailable.add(rangeInfo);
}
}
final String statusText = (myExecutor != null ? myExecutor.getExecutionDescription() : null);
myView.configureSubTexts(statusText, placesWhereSuggestionAvailable);
}
@Override
public void suggestionRequested() {
configureSuggestion(true); // Show or hide
}
/**
* Displays suggestions if needed.
*
* @param requestedExplicitly is suggesions where requested by user explicitly or not
*/
private void configureSuggestion(final boolean requestedExplicitly) {
myView.removeSuggestions();
final RangeInfo rangeInfo = findNearestRangeInfo();
final String text = getTextByRange(rangeInfo);
final SuggestionInfo suggestionInfo = rangeInfo.getSuggestions();
if (suggestionInfo == null || (!suggestionInfo.isShowSuggestionsAutomatically() && !requestedExplicitly)) {
return;
}
final List<String> suggestions = new ArrayList<String>(suggestionInfo.getSuggestions());
if (text != null && !requestedExplicitly) {
filterLeaveOnlyMatching(suggestions, text);
}
// TODO: Place to add history
if (!suggestions.isEmpty()) {
// No need to display empty suggestions
myView
.displaySuggestions(new SuggestionsBuilder(suggestions), suggestionInfo.isShowAbsolute(), text);
}
}
/**
* Filters collection of suggestions leaving only those starts with certain text.
*
* @param suggestions list to filter
* @param textToMatch leave only parts that start with this param
*/
private static void filterLeaveOnlyMatching(@NotNull final Iterable<String> suggestions, @NotNull final String textToMatch) {
// TODO: use guava instead?
final Iterator<String> iterator = suggestions.iterator();
while (iterator.hasNext()) {
if (!iterator.next().startsWith(textToMatch)) {
iterator.remove();
}
}
}
/**
* Searches for the nearest range to use.
*
* @return nearest range info
*/
@NotNull
private RangeInfo findNearestRangeInfo() {
final int caretPosition = myView.getCaretPosition();
for (final RangeInfo range : myRangeInfos) {
if (range.isWithin(caretPosition)) {
return range;
}
if (range.getFrom() > caretPosition) {
return range; // Ranges are sorted, so we are on the next range. Take it, if caret is not within range
}
}
return myRangeInfos.last();
}
@Override
public void completionRequested(@Nullable final String valueFromSuggestionList) {
final RangeInfo rangeInfo = findNearestRangeInfo();
final String text = getTextByRange(rangeInfo);
if (valueFromSuggestionList != null) {
// Just insert it
if (text != null) { // If caret is on the text itself
// TODO: Replace next range, if it has no space before it(--a=12 should be replaced wth arg)
myView.replaceText(rangeInfo.getFrom(), rangeInfo.getTo(), valueFromSuggestionList);
}
else {
myView.insertTextAfterCaret(valueFromSuggestionList);
}
return;
}
//User did not provide text no insert, do our best to find one
final SuggestionInfo suggestionInfo = rangeInfo.getSuggestions();
if (suggestionInfo == null) {
return; // No suggestion available for this chunk
}
final List<String> suggestions = new ArrayList<String>(suggestionInfo.getSuggestions());
if (text != null) {
filterLeaveOnlyMatching(suggestions, text);
}
if (suggestions.size() == 1) {
// Exclusive!
if (text != null) {
// TODO: Replace next range, if it has no space before it(--a=12 should be replaced wth arg)
myView.replaceText(rangeInfo.getFrom(), rangeInfo.getTo(), suggestions.get(0));
}
else {
myView.insertTextAfterCaret(suggestions.get(0));
}
}
}
/**
* Searches for text under the range
* @param rangeInfo range
* @return text or null if range does not contain any text
*/
@Nullable
private String getTextByRange(@NotNull final RangeInfo rangeInfo) {
if (rangeInfo.isTerminationRange()) {
return null;
}
else {
final String viewText = myView.getText();
return viewText.substring(rangeInfo.getFrom(), rangeInfo.getTo());
}
}
@Override
public void executionRequested() {
if (myExecutor == null) {
// TODO: Display error somehow
}
else {
myExecutor.execute();
}
}
}
@@ -0,0 +1,122 @@
/*
* Copyright 2000-2015 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.commandInterface.rangeBasedPresenter;
import com.intellij.util.Range;
import com.jetbrains.python.commandInterface.CommandInterfaceView;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Information about certain place in text, provided by driver.
*
* @author Ilya.Kazakevich
*/
public final class RangeInfo extends Range<Integer> implements Comparable<RangeInfo> {
/**
* Special "terminator": the last range which is after all other ranges.
*/
public static final Range<Integer> TERMINATION_RANGE = CommandInterfaceView.AFTER_LAST_CHARACTER_RANGE;
@Nullable
private final String myInfoBalloon;
@Nullable
private final String myError;
@Nullable
private final SuggestionInfo mySuggestions;
private final boolean myExclusiveBorders;
/**
* @param infoBalloon info to bind to this range
* @param error error to bind to this range
* @param exclusiveBorders true if range has exclusive borders and right border is not part of it. I.e. 3 is part of 1-3 range with out
* of exclusive borders, but not part of exclusive range
* @param range from and to
*/
public RangeInfo(@Nullable final String infoBalloon,
@Nullable final String error,
final boolean exclusiveBorders,
@NotNull final Range<Integer> range) {
this(infoBalloon, error, null, range, exclusiveBorders);
}
/**
* @param infoBalloon Info balloon to display when caret meets this place (null if display nothing)
* @param error Error balloon to display when caret meets this place and underline text as error (null if no error)
* @param suggestions list of suggestions available in this place (if any)
*/
public RangeInfo(@Nullable final String infoBalloon,
@Nullable final String error,
@Nullable final SuggestionInfo suggestions,
@NotNull final Range<Integer> range,
final boolean exclusiveBorders) {
super(range.getFrom(), range.getTo());
myInfoBalloon = infoBalloon;
myError = error;
mySuggestions = suggestions;
myExclusiveBorders = exclusiveBorders;
}
/**
* @return Info balloon to display when caret meets this place (null if display nothing)
*/
@Nullable
public String getInfoBalloon() {
return myInfoBalloon;
}
/**
* @return Error balloon to display when caret meets this place and underline text as error (null if no error)
*/
@Nullable
public String getError() {
return myError;
}
/**
* @return list of suggestions available in this place (if any)
*/
@Nullable
public SuggestionInfo getSuggestions() {
return mySuggestions;
}
@Override
public int compareTo(RangeInfo o) {
return getFrom().compareTo(o.getFrom());
}
@Override
public boolean isWithin(final Integer object) {
if (!super.isWithin(object)) {
return false;
}
if (myExclusiveBorders) {
return object < getTo();
}
return true;
}
public boolean isTerminationRange() {
// TODO: copy/paste with view
return TERMINATION_RANGE.getFrom().equals(getFrom()) && TERMINATION_RANGE.getTo().equals(getTo());
}
}
@@ -0,0 +1,39 @@
/*
* Copyright 2000-2015 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.commandInterface.rangeBasedPresenter;
import com.intellij.openapi.util.Pair;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* Driver that knows how to getCommandLineInfo pack of chunks into chunk info.
*
* @author Ilya.Kazakevich
*/
public interface RangeInfoDriver {
/**
* Parses command line text into executor and pack of range infos.
* There <strong>always</strong> should be at least one range info and the last one is almost always {@link RangeInfo#TERMINATION_RANGE).
*
* @param commandLineText command line text to parse.
* @return pair or executor (the one that knows how to execute command line) and pack of range infos.
* <strong>Warning! </strong> :Executor could be null if command can't be executed
*/
@NotNull
Pair<Executor, List<RangeInfo>> getCommandLineInfo(@NotNull String commandLineText);
}
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.commandInterface.chunkDriverBasedPresenter;
package com.jetbrains.python.commandInterface.rangeBasedPresenter;
import org.jetbrains.annotations.NotNull;
@@ -67,4 +67,13 @@ public final class SuggestionInfo {
public boolean isShowAbsolute() {
return myShowAbsolute;
}
@Override
public String toString() {
return "SuggestionInfo{" +
"mySuggestions=" + mySuggestions +
", myShowSuggestionsAutomatically=" + myShowSuggestionsAutomatically +
", myShowAbsolute=" + myShowAbsolute +
'}';
}
}
@@ -0,0 +1,32 @@
/*
* Copyright 2000-2015 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.
*/
/**
* {@link com.jetbrains.python.commandInterface.CommandInterfacePresenter} implementation based on ideas of <strong>range info</strong>
* and {@link com.jetbrains.python.commandInterface.rangeBasedPresenter.RangeInfoDriver}.
* This presenter passes command line to driver, and it returns pack of range information.
* Each record contains everything presenter needs to know about certain range (i.e. error between 2 and 5 chars).
* Special type of range {@link com.jetbrains.python.commandInterface.rangeBasedPresenter.RangeInfo#TERMINATION_RANGE} exists, that
* you may need to check.
* <p/>
* Presenter uses this information to display chunks correctly using view.
* To use this package, {@link com.jetbrains.python.commandInterface.rangeBasedPresenter.RangeInfoDriver} should be implemented.
* <p/>
* See {@link com.jetbrains.python.commandInterface.rangeBasedPresenter.RangeBasedPresenter} as entry point
*
* @author Ilya.Kazakevich
*/
package com.jetbrains.python.commandInterface.rangeBasedPresenter;
@@ -15,7 +15,7 @@
*/
package com.jetbrains.python.commandLineParser;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.jetbrains.python.WordWithPosition;
import org.jetbrains.annotations.NotNull;
@@ -25,22 +25,22 @@ import java.util.Collections;
import java.util.List;
/**
* Command line parse result.
* Command line getCommandLineInfo result.
* It consists of command itself and its parts.
* Each part may be {@link com.jetbrains.python.commandLineParser.CommandLinePartType#ARGUMENT argument} or
* {@link com.jetbrains.python.commandLineParser.CommandLinePartType#OPTION option} or something else.
*
* @author Ilya.Kazakevich
*/
public final class CommandLineParseResult {
public final class CommandLine {
@NotNull
private final List<Pair<CommandLinePartType, WordWithPosition>> myParts = new ArrayList<Pair<CommandLinePartType, WordWithPosition>>();
private final List<CommandLinePart> myParts = new ArrayList<CommandLinePart>();
@NotNull
private final WordWithPosition myCommand;
public CommandLineParseResult(
public CommandLine(
@NotNull final WordWithPosition command,
@NotNull final Collection<Pair<CommandLinePartType, WordWithPosition>> parts) {
@NotNull final Collection<CommandLinePart> parts) {
myCommand = command;
myParts.addAll(parts);
}
@@ -53,26 +53,26 @@ public final class CommandLineParseResult {
return myCommand;
}
/**
* @return list of parts in format [part_type, value].
* For example (rm my_folder): [{@link com.jetbrains.python.commandLineParser.CommandLinePartType#ARGUMENT argument}, my_folder]
*/
@NotNull
public List<Pair<CommandLinePartType, WordWithPosition>> getParts() {
public List<CommandLinePart> getParts() {
return Collections.unmodifiableList(myParts);
}
/**
* @return all command line parts with out of part information (just words and positions).
* Note tha command itself is not part, only args and options are
* @see #getParts()
* @return all command parts as text (actually by bindning them back together)
*/
@NotNull
public Collection<WordWithPosition> getPartsNoType() {
final Collection<WordWithPosition> result = new ArrayList<WordWithPosition>();
for (final Pair<CommandLinePartType, WordWithPosition> part : myParts) {
result.add(part.second);
public String getPartsAsText() {
final StringBuilder builder = new StringBuilder();
int lastPosition = 0;
for (final CommandLinePart part : getParts()) {
final WordWithPosition partWord = part.getWord();
if (lastPosition != partWord.getFrom()) {
builder.append(' ');
}
builder.append(partWord.getText());
lastPosition = partWord.getTo();
}
return result;
return StringUtil.trim(builder.toString());
}
}
@@ -0,0 +1,36 @@
/*
* Copyright 2000-2015 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.commandLineParser;
import com.jetbrains.python.WordWithPosition;
import org.jetbrains.annotations.NotNull;
/**
* Positional or option argument.
* @author Ilya.Kazakevich
*/
public final class CommandLineArgument extends CommandLinePart {
public CommandLineArgument(@NotNull final WordWithPosition argumentValue) {
super(argumentValue);
}
@Override
public void accept(@NotNull final CommandLinePartVisitor visitor) {
visitor.visitArgument(this);
}
}
@@ -0,0 +1,69 @@
/*
* Copyright 2000-2015 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.commandLineParser;
import com.jetbrains.python.WordWithPosition;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Option. Each option has name and may also have attached argument like "--long-option=attached_arg" or "-sATTACHED_ARG"
*
* @author Ilya.Kazakevich
*/
public final class CommandLineOption extends CommandLinePart {
@NotNull
private final String myOptionName;
@Nullable
private final WordWithPosition myAttachedArgument;
/**
* @param option option (text and position)
* @param optionName option name (like "--foo")
* @param attachedArgument option attached argument (like --foo=ATTACHED_ARG)
*/
public CommandLineOption(@NotNull final WordWithPosition option,
@NotNull final String optionName,
@Nullable final WordWithPosition attachedArgument) {
super(option);
myOptionName = optionName;
myAttachedArgument = attachedArgument;
}
/**
* @return option name (like "--foo")
*/
@NotNull
public String getOptionName() {
return myOptionName;
}
/**
* @return option attached argument (like --foo=ATTACHED_ARG)
*/
@Nullable
public WordWithPosition getAttachedArgument() {
return myAttachedArgument;
}
@Override
public void accept(@NotNull final CommandLinePartVisitor visitor) {
visitor.visitOption(this);
}
}
@@ -15,24 +15,20 @@
*/
package com.jetbrains.python.commandLineParser;
import com.jetbrains.python.WordWithPosition;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* Engine to parse command line. It understands how options and arguments are coded in certain commandline.
* It supportd {@link com.jetbrains.python.WordWithPosition} telling you exactly with part of
* command line is command or argument. That helps you to underline or emphisize some parts.
* Engine to {@link CommandLine} structure from text.
*
* @author Ilya.Kazakevich
*/
public interface CommandLineParser {
/**
* @param commandLineParts command line splitted into words.
*
* @param commandLineText command line to parse
* @return command line information
* @throws MalformedCommandLineException in case of bad commandline
*/
@NotNull
CommandLineParseResult parse(@NotNull List<WordWithPosition> commandLineParts) throws MalformedCommandLineException;
CommandLine parse(@NotNull String commandLineText) throws MalformedCommandLineException;
}
@@ -13,27 +13,39 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.commandInterface.chunkDriverBasedPresenter;
package com.jetbrains.python.commandLineParser;
import com.jetbrains.python.WordWithPosition;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* Driver that knows how to parse pack of chunks into chunk info.
* Part of command line. Known subclasses are {@link CommandLineOption} and {@link CommandLineArgument}
*
* @author Ilya.Kazakevich
* @see CommandLineArgument
* @see CommandLineOption
*/
public interface ChunkDriver {
public abstract class CommandLinePart {
@NotNull
private final WordWithPosition myWord;
/**
* Parses chunks into pack of chunks. There <strong>always</strong> should be chunk+1 chunkInfos (one for the tail like
* {@link com.jetbrains.python.commandInterface.CommandInterfaceView#AFTER_LAST_CHARACTER_RANGE}).
* So, at least one chunk info should also exist!
*
* @param chunks chunks (parts of command line)
* @return parse info with chunks info. Warning: do not return less chunk infos than chunks provided. That leads to runtime error
* @param word word (and its position) this part represents
*/
protected CommandLinePart(@NotNull final WordWithPosition word) {
myWord = word;
}
/**
* @return word (and its position) this part represents
*/
@NotNull
ParseInfo parse(@NotNull List<WordWithPosition> chunks);
public final WordWithPosition getWord() {
return myWord;
}
/**
* @param visitor visitor to accept
*/
public abstract void accept(@NotNull CommandLinePartVisitor visitor);
}
@@ -15,27 +15,23 @@
*/
package com.jetbrains.python.commandLineParser;
import org.jetbrains.annotations.NotNull;
/**
* Types of command line parts.
* Visitor to process options and arguments
*
* @author Ilya.Kazakevich
* @see CommandLinePart#accept(CommandLinePartVisitor)
*/
public enum CommandLinePartType {
public interface CommandLinePartVisitor {
/**
* Argument (or positional, or unnamed argument) something that has only value. Like "my_folder" in "rm my_folder"
* @param option option to visit
*/
ARGUMENT,
void visitOption(@NotNull CommandLineOption option);
/**
* Option is named but optional parameter. Like "-l" in "ls -l".
* @param argument argument to visit
*/
OPTION,
/**
* Option argument like --folder-to-delete=/
* Here root is option argument
*/
OPTION_ARGUMENT,
/**
* Some part of command line that {@link com.jetbrains.python.commandLineParser.CommandLineParser} does not understand
*/
UNKNOWN
void visitArgument(@NotNull CommandLineArgument argument);
}
@@ -16,38 +16,27 @@
package com.jetbrains.python.commandLineParser.optParse;
import com.intellij.openapi.util.Pair;
import com.jetbrains.python.commandInterface.command.Option;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Supports --long-option-style-with=value
*
* @author Ilya.Kazakevich
*/
final class LongOptionParser implements OptionParser {
@Nullable
final class LongOptionParser extends OptionParserRegexBased {
@NotNull
private static final Pattern LONG_OPT_PATTERN = Pattern.compile("^((--[a-zA-Z0-9-]+)=?)");
LongOptionParser() {
super(LONG_OPT_PATTERN);
}
@NotNull
@Override
public Pair<Option, String> findOptionAndValue(@NotNull final List<Option> availableOptions, @NotNull final String textToCheck) {
if (!textToCheck.startsWith("--")) {
return null;
}
for (final Option option : availableOptions) {
for (final String longName : option.getLongNames()) {
if (textToCheck.startsWith(longName)) {
final String[] parts = textToCheck.split("=");
if (parts.length == 0) {
return Pair.create(option, null);
}
else {
return Pair.create(option, parts[1]);
}
}
}
}
return null;
protected Pair<String, String> getOptionTextAndNameFromMatcher(@NotNull final Matcher matcher) {
return Pair.create(matcher.group(1), matcher.group(2));
}
}
@@ -16,19 +16,15 @@
package com.jetbrains.python.commandLineParser.optParse;
import com.intellij.openapi.util.Pair;
import com.intellij.util.containers.hash.HashMap;
import com.jetbrains.python.WordWithPosition;
import com.jetbrains.python.commandInterface.command.Command;
import com.jetbrains.python.commandInterface.command.Option;
import com.jetbrains.python.commandInterface.command.OptionArgumentInfo;
import com.jetbrains.python.commandLineParser.CommandLineParseResult;
import com.jetbrains.python.commandLineParser.CommandLineParser;
import com.jetbrains.python.commandLineParser.CommandLinePartType;
import com.jetbrains.python.commandLineParser.MalformedCommandLineException;
import com.jetbrains.python.commandLineParser.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
/**
* <p>
@@ -48,114 +44,67 @@ import java.util.*;
public final class OptParseCommandLineParser implements CommandLineParser {
/**
* Supported option parsers (option types, actually)
* Short (-o) and long (--option) strategies are used here
*/
@NotNull
private static final OptionParser[] OPTION_PARSERS = {new LongOptionParser(), new ShortOptionParser()};
/**
* CommandName -> Options
*/
@NotNull
private final Map<String, List<Option>> myCommandOptions = new HashMap<String, List<Option>>();
private static final OptionParser[] OPTION_PARSERS = {new ShortOptionParser(), new LongOptionParser()};
/**
* @param commands commands (needed to parse out options)
*/
public OptParseCommandLineParser(@NotNull final Iterable<? extends Command> commands) {
for (final Command command : commands) {
myCommandOptions.put(command.getName(), command.getOptions());
}
}
@NotNull
@Override
public CommandLineParseResult parse(@NotNull final List<WordWithPosition> commandLineParts) throws MalformedCommandLineException {
final Deque<WordWithPosition> parts = new ArrayDeque<WordWithPosition>(commandLineParts);
public CommandLine parse(@NotNull final String commandLineText) throws MalformedCommandLineException {
final Deque<WordWithPosition> parts = new ArrayDeque<WordWithPosition>(WordWithPosition.splitText(commandLineText));
if (parts.isEmpty()) {
throw new MalformedCommandLineException("No command provided");
}
final WordWithPosition command = parts.pop();
final List<Option> options;
if (myCommandOptions.containsKey(command.getText())) {
options = myCommandOptions.get(command.getText());
}
else {
options = Collections.emptyList();
}
final List<CommandLinePart> optionsAndArguments = new ArrayList<CommandLinePart>();
final List<Pair<CommandLinePartType, WordWithPosition>> resultParts = new ArrayList<Pair<CommandLinePartType, WordWithPosition>>();
if (isOption(command)) {
throw new MalformedCommandLineException("Command can't start with option prefix");
}
int optionArgumentsLeft = 0; // If option has N arguments, then next N arguments belong to this option
for (final WordWithPosition part : parts) {
if (optionArgumentsLeft > 0) {
optionArgumentsLeft--;
resultParts.add(Pair.create(CommandLinePartType.OPTION_ARGUMENT, part));
continue;
}
if (isOption(part)) {
// This is option!
final Pair<Option, String> optionAndValue = findOptionAndValue(options, part.getText());
if (optionAndValue != null) {
final Option option = optionAndValue.first;
final Pair<Integer, OptionArgumentInfo> argumentAndQuantity = option.getArgumentAndQuantity();
if (argumentAndQuantity != null) {
optionArgumentsLeft = argumentAndQuantity.first;
}
final String optionArgumentValue = optionAndValue.second;
if (optionArgumentValue != null) {
// We found argument
optionArgumentsLeft--;
final String optionArgumentText =
part.getText().substring(0, part.getText().length() - optionArgumentValue.length());
final WordWithPosition optionPart =
new WordWithPosition(optionArgumentText, part.getFrom(), part.getFrom() + optionArgumentText.length());
final WordWithPosition valuePart = new WordWithPosition(optionArgumentValue, optionPart.getTo(), part.getTo());
resultParts.add(Pair.create(CommandLinePartType.OPTION, optionPart));
resultParts.add(Pair.create(CommandLinePartType.OPTION_ARGUMENT, valuePart));
}
else {
resultParts.add(Pair.create(CommandLinePartType.OPTION, part));
}
final Pair<String, String> optionTextAndName = findOptionTextAndName(part.getText());
if (optionTextAndName != null) {
final String optionText = optionTextAndName.first;
final String optionName = optionTextAndName.second;
final WordWithPosition option = new WordWithPosition(optionText, part.getFrom(), part.getFrom() + optionText.length());
final WordWithPosition optionArgument;
if (optionText.length() == part.getText().length()) {
optionArgument = null;
}
else {
resultParts.add(Pair.create(CommandLinePartType.UNKNOWN, part));
// Looks like we have option argument here.
final String argumentText = part.getText().substring(optionText.length());
optionArgument = new WordWithPosition(argumentText, option.getTo(), part.getTo());
}
optionsAndArguments.add(new CommandLineOption(option, optionName, optionArgument)); //Option
if (optionArgument != null) {
optionsAndArguments.add(new CommandLineArgument(optionArgument)); // And its argument
}
}
else {
resultParts.add(Pair.create(CommandLinePartType.ARGUMENT, part));
optionsAndArguments.add(new CommandLineArgument(part)); //Not an option. Should be argument
}
}
return new CommandLineParseResult(command, resultParts);
return new CommandLine(command, optionsAndArguments);
}
/**
* Parse out option and its value iterating through the parsers
*
* @param options all available options
* @param text text believed to be an option like "--foo=123"
* @return [option->argument_value] pair or null if provided text is not an option. ArgValue may also be null if not provided
* @see com.jetbrains.python.commandLineParser.optParse.OptionParser#findOptionAndValue(java.util.List, String)
* @param text text believed to be an option like "--foo=123"
* @return [option_text, option_name] or null of no such option
* @see OptionParser#findOptionTextAndName(String)
*/
@Nullable
private static Pair<Option, String> findOptionAndValue(@NotNull final List<Option> options, @NotNull final String text) {
private static Pair<String, String> findOptionTextAndName(@NotNull final String optionText) {
for (final OptionParser parser : OPTION_PARSERS) {
final Pair<Option, String> optionAndValue = parser.findOptionAndValue(options, text);
if (optionAndValue != null) {
return optionAndValue;
final Pair<String, String> optionTextAndName = parser.findOptionTextAndName(optionText);
if (optionTextAndName != null) {
return optionTextAndName;
}
}
return null;
}
/**
* @param command command to check
* @return if command is option or not
*/
private static boolean isOption(@NotNull final WordWithPosition command) {
return command.getText().startsWith("-");
}
}
@@ -16,25 +16,19 @@
package com.jetbrains.python.commandLineParser.optParse;
import com.intellij.openapi.util.Pair;
import com.jetbrains.python.commandInterface.command.Option;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
/**
* Engine that knows how to deal with option of certain style
* Engine that knows how to deal with option of certain style (like long and short)
*
* @author Ilya.Kazakevich
*/
interface OptionParser {
/**
* Checks if some text that looks like and option is really option
*
* @param availableOptions all available options
* @param textToCheck text believed to be an option like "--foo=123"
* @return [option->argument_value] pair or null if provided text is not an option. ArgValue may also be null if not provided
* @param optionText text to parse (like --foo=bar)
* @return null if option can't be parsed. Otherwise pair of [option_text, option_name]. That may match each other in some cases.
*/
@Nullable
Pair<Option, String> findOptionAndValue(@NotNull List<Option> availableOptions, @NotNull String textToCheck);
Pair<String, String> findOptionTextAndName(@NotNull String optionText);
}
@@ -0,0 +1,60 @@
/*
* Copyright 2000-2015 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.commandLineParser.optParse;
import com.intellij.openapi.util.Pair;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Parses options using regex and template method
*
* @author Ilya.Kazakevich
*/
abstract class OptionParserRegexBased implements OptionParser {
@NotNull
private final Pattern myPattern;
/**
* @param pattern regex. If option matches pattern, ({@link Matcher#find() opened matcher}) would be passed to template method
* {@link #getOptionTextAndNameFromMatcher(Matcher)}
*/
protected OptionParserRegexBased(@NotNull final Pattern pattern) {
myPattern = pattern;
}
@Nullable
@Override
public final Pair<String, String> findOptionTextAndName(@NotNull final String optionText) {
final Matcher matcher = myPattern.matcher(optionText);
if (!matcher.find()) {
return null;
}
return getOptionTextAndNameFromMatcher(matcher);
}
/**
* Obtains [option_text, option_name] from matcher
*
* @param matcher opened (with find() called) matcher
* @return pair [option_text, option_name]
*/
@NotNull
protected abstract Pair<String, String> getOptionTextAndNameFromMatcher(@NotNull Matcher matcher);
}
@@ -16,44 +16,29 @@
package com.jetbrains.python.commandLineParser.optParse;
import com.intellij.openapi.util.Pair;
import com.jetbrains.python.commandInterface.command.Option;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Deals with short-style options like -b or -f
*
* @author Ilya.Kazakevich
*/
final class ShortOptionParser implements OptionParser {
final class ShortOptionParser extends OptionParserRegexBased {
/**
* Short-style option regexp
*/
private static final Pattern SHORT_OPT_START = Pattern.compile("^(-[a-zA-Z0-9])([^ -])?");
private static final Pattern SHORT_OPT_PATTERN = Pattern.compile("^(-[a-zA-Z0-9])([^ -])?");
@Nullable
ShortOptionParser() {
super(SHORT_OPT_PATTERN);
}
@NotNull
@Override
public Pair<Option, String> findOptionAndValue(@NotNull final List<Option> availableOptions, @NotNull final String textToCheck) {
final Matcher matcher = SHORT_OPT_START.matcher(textToCheck);
if (!matcher.find()) {
return null;
}
final String optionText = matcher.group(1);
final String optionValueText = (matcher.groupCount() == 2 ? matcher.group(2) : null);
for (final Option option : availableOptions) {
for (final String shortName : option.getShortNames()) {
if (optionText.equals(shortName)) {
return Pair.create(option, optionValueText);
}
}
}
return null;
protected Pair<String, String> getOptionTextAndNameFromMatcher(@NotNull final Matcher matcher) {
return Pair.create(matcher.group(1), matcher.group(1));
}
}
@@ -15,16 +15,15 @@
*/
/**
* Engine to parse command-line.
* Command line consists of command itself, {@link com.jetbrains.python.commandLineParser.CommandLinePartType#ARGUMENT arguments}
* and {@link com.jetbrains.python.commandLineParser.CommandLinePartType#OPTION options}.
* Use need to split command line into {@link com.jetbrains.python.WordWithPosition chunks}, pass them to
* {@link com.jetbrains.python.commandLineParser.CommandLineParser parser} and obtain {@link com.jetbrains.python.commandLineParser.CommandLineParseResult}.
* Engine to parse commandline from string to {@link com.jetbrains.python.commandLineParser.CommandLine} structure.
*
* Command line consists of command itself, {@link com.jetbrains.python.commandLineParser.CommandLineArgument arguments}
* and {@link com.jetbrains.python.commandLineParser.CommandLineOption options}.
* Use need to pass text to {@link com.jetbrains.python.commandLineParser.CommandLineParser parser} and obtain {@link com.jetbrains.python.commandLineParser.CommandLine}.
* <p/>
* Not like any other parsers, this package supports {@link com.jetbrains.python.WordWithPosition} telling you exactly with part of
* command line is command or argument. That helps you to underline or emphisize some parts.
*
*
* @author Ilya.Kazakevich
*/
package com.jetbrains.python.commandLineParser;
@@ -99,6 +99,7 @@ public class SuggestionList {
}
// Fill and select
int record = 0; // Record to select
// Iterate through groups adding suggestions. Odd groups should be marked differently.
for (int groupId = 0; groupId < values.size(); groupId++) {
final List<Suggestion> suggestions = values.get(groupId);
@@ -106,11 +107,12 @@ public class SuggestionList {
final Suggestion suggestion = suggestions.get(suggestionId);
myListModel.addElement(new SuggestionListElement((groupId % 2) == 0, suggestion));
if (suggestion.getText().equals(elementToSelect)) {
myList.setSelectedIndex(suggestionId + groupId);
myList.setSelectedIndex(record);
}
record++;
}
}
if ((elementToSelect == null) && (!myListModel.isEmpty())) {
if ((myList.getSelectedIndex() ==-1) && (!myListModel.isEmpty())) {
myList.setSelectedIndex(0); // Select first element
}
@@ -124,6 +126,7 @@ public class SuggestionList {
myListPopUp.addListener(myListener);
}
myListPopUp.show(displayPoint);
myList.ensureIndexIsVisible(myList.getSelectedIndex()); // Scrolls to selected
}