repo-reorg-2020: moved sts4-relevant spring-ide bundles from https://github.com/spring-projects/spring-ide/releases/tag/REPO-REORG-2020 into sts4 repo

This commit is contained in:
Martin Lippert
2020-09-29 14:00:01 +02:00
parent 8edfab1a16
commit de16884b60
2210 changed files with 160282 additions and 43 deletions

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="src" path="src"/>
<classpathentry kind="output" path="bin"/>
</classpath>

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.springframework.ide.eclipse.editor.support</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.ManifestBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.SchemaBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.pde.PluginNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

View File

@@ -0,0 +1,7 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
org.eclipse.jdt.core.compiler.compliance=1.8
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.source=1.8

View File

@@ -0,0 +1,60 @@
eclipse.preferences.version=1
editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
sp_cleanup.add_default_serial_version_id=true
sp_cleanup.add_generated_serial_version_id=false
sp_cleanup.add_missing_annotations=false
sp_cleanup.add_missing_deprecated_annotations=true
sp_cleanup.add_missing_methods=false
sp_cleanup.add_missing_nls_tags=false
sp_cleanup.add_missing_override_annotations=true
sp_cleanup.add_missing_override_annotations_interface_methods=true
sp_cleanup.add_serial_version_id=false
sp_cleanup.always_use_blocks=true
sp_cleanup.always_use_parentheses_in_expressions=false
sp_cleanup.always_use_this_for_non_static_field_access=false
sp_cleanup.always_use_this_for_non_static_method_access=false
sp_cleanup.convert_functional_interfaces=false
sp_cleanup.convert_to_enhanced_for_loop=false
sp_cleanup.correct_indentation=false
sp_cleanup.format_source_code=false
sp_cleanup.format_source_code_changes_only=false
sp_cleanup.insert_inferred_type_arguments=false
sp_cleanup.make_local_variable_final=true
sp_cleanup.make_parameters_final=false
sp_cleanup.make_private_fields_final=true
sp_cleanup.make_type_abstract_if_missing_method=false
sp_cleanup.make_variable_declarations_final=false
sp_cleanup.never_use_blocks=false
sp_cleanup.never_use_parentheses_in_expressions=true
sp_cleanup.on_save_use_additional_actions=true
sp_cleanup.organize_imports=false
sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
sp_cleanup.remove_private_constructors=true
sp_cleanup.remove_redundant_type_arguments=true
sp_cleanup.remove_trailing_whitespaces=true
sp_cleanup.remove_trailing_whitespaces_all=true
sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
sp_cleanup.remove_unnecessary_casts=false
sp_cleanup.remove_unnecessary_nls_tags=false
sp_cleanup.remove_unused_imports=false
sp_cleanup.remove_unused_local_variables=false
sp_cleanup.remove_unused_private_fields=true
sp_cleanup.remove_unused_private_members=false
sp_cleanup.remove_unused_private_methods=true
sp_cleanup.remove_unused_private_types=true
sp_cleanup.sort_members=false
sp_cleanup.sort_members_all=false
sp_cleanup.use_anonymous_class_creation=false
sp_cleanup.use_blocks=false
sp_cleanup.use_blocks_only_for_return_and_throw=false
sp_cleanup.use_lambda=true
sp_cleanup.use_parentheses_in_expressions=false
sp_cleanup.use_this_for_non_static_field_access=false
sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
sp_cleanup.use_this_for_non_static_method_access=false
sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
sp_cleanup.use_type_arguments=false

View File

@@ -0,0 +1,51 @@
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: %pluginName
Bundle-Vendor: %providerName
Bundle-SymbolicName: org.springframework.ide.eclipse.editor.support
Bundle-Version: 4.8.1.qualifier
Bundle-Activator: org.springframework.ide.eclipse.editor.support.EditorSupportActivator
Bundle-Localization: plugin
Require-Bundle: org.eclipse.ui,
org.eclipse.text,
org.eclipse.jface.text,
org.yaml.snakeyaml,
com.google.guava,
org.eclipse.core.resources,
org.eclipse.ui.ide,
org.eclipse.jdt.ui,
org.eclipse.jdt.core,
org.dadacoalition.yedit,
org.eclipse.ui.editors,
org.springsource.ide.eclipse.commons.livexp;bundle-version="3.8.4",
org.springsource.ide.eclipse.commons.frameworks.core,
org.springsource.ide.eclipse.commons.core,
org.springframework.ide.eclipse.boot,
io.projectreactor.reactor-core;bundle-version="[3.3.1,3.3.2)",
org.reactivestreams.reactive-streams;bundle-version="1.0.0",
org.apache.commons.lang3,
org.springsource.ide.eclipse.commons.ui,
javax.inject
Bundle-ActivationPolicy: lazy
Export-Package: org.springframework.ide.eclipse.editor.support,
org.springframework.ide.eclipse.editor.support.completions,
org.springframework.ide.eclipse.editor.support.hover,
org.springframework.ide.eclipse.editor.support.preferences,
org.springframework.ide.eclipse.editor.support.reconcile,
org.springframework.ide.eclipse.editor.support.util,
org.springframework.ide.eclipse.editor.support.yaml,
org.springframework.ide.eclipse.editor.support.yaml.ast,
org.springframework.ide.eclipse.editor.support.yaml.completions,
org.springframework.ide.eclipse.editor.support.yaml.hover,
org.springframework.ide.eclipse.editor.support.yaml.path,
org.springframework.ide.eclipse.editor.support.yaml.reconcile,
org.springframework.ide.eclipse.editor.support.yaml.schema,
org.springframework.ide.eclipse.editor.support.yaml.schema.constraints,
org.springframework.ide.eclipse.editor.support.yaml.structure
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Automatic-Module-Name: org.springframework.ide.eclipse.editor.support
Import-Package: org.eclipse.core.runtime,
org.eclipse.core.runtime.jobs,
org.eclipse.core.runtime.preferences,
org.osgi.service.prefs,
org.osgi.framework

View File

@@ -0,0 +1,248 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
<title>About Spring IDE</title>
<meta name="author" content="Spring IDE Developers">
</head>
<body>
<h2>About Spring IDE</h2>
<p>March 26, 2007</p>
<h3>Abstract</h3>
<p>Spring IDE is a set of plugins which provide a user interface for
<a href="https://www.springframework.org">The Spring Framework</a>'s bean
factory XML configuration files including support for Spring 2.0
namespaces, AOP configuration and Spring Web Flow.</p>
<h3>License</h3>
<p>The Spring IDE Project makes available all content in this
plug-in (&quot;Content&quot;). Unless otherwise indicated below, the
Content is provided to you under the terms and conditions of the Eclipse
Public License Version 1.0 (&quot;EPL&quot;). A copy of the EPL is
available at <a href="https://www.eclipse.org/legal/epl-v10.html">https://www.eclipse.org/legal/epl-v10.html</a>.
For purposes of the EPL, &quot;Program&quot; will mean the Content.</p>
<p>If you did not receive this Content directly from the Spring IDE
Project, the Content is being redistributed by another party
(&quot;Redistributor&quot;) and different terms and conditions may apply
to your use of any object code in the Content. Check the Redistributor's
license that was provided with the Content. If no such license exists,
contact the Redistributor. Unless otherwise indicated below, the terms
and conditions of the EPL still apply to any source code in the Content
and such source code may be obtained at <a href="https://www.eclipse.org">https://www.eclipse.org</a>.</p>
<h3>Third Party Content</h3>
<p>The Content includes items that have been sourced from third
parties as set out below. If you did not receive this Content directly
from the Spring IDE Project, the following is provided for informational
purposes only, and you should look to the Redistributor's license for
terms and conditions of use.</p>
<p><em> <strong>The Spring Framework v3.0.5</strong>
<p>This product includes software developed by the Spring Framework
Project (<a href="https://www.springframework.org">https://www.springframework.org</a>).</p>
<p><em> <strong>Spring Web Flow v2.0.7.A</strong>
<p>This product includes software developed by the Spring Framework
Project (<a href="https://www.springframework.org">https://www.springframework.org</a>).</p>
<p><em> <strong>Spring Web Services v1.5.8</strong>
<p>This product includes software developed by the Spring Framework
Project (<a href="https://www.springframework.org">https://www.springframework.org</a>).</p>
<p><em> <strong>Spring Security v3.0.2</strong>
<p>This product includes software developed by the Spring Framework
Project (<a href="https://www.springframework.org">https://www.springframework.org</a>).</p>
<p><em> <strong>Spring Dynamic Modules for OSGi(TM) Runtimes v2.0.0.M2</strong>
<p>This product includes software developed by the Spring Framework
Project (<a href="https://www.springframework.org">https://www.springframework.org</a>).</p>
<p><em> <strong>Spring Batch v2.1.0</strong>
<p>This product includes software developed by the Spring Framework
Project (<a href="https://www.springframework.org">https://www.springframework.org</a>).</p>
<p><em> <strong>Commons Codec v1.3.0</strong>
<p>This product includes software developed by The Apache Software
Foundation (<a href="https://www.apache.org/">https://www.apache.org/</a>).</p>
<p><em> <strong>Commons Collections v3.2.0</strong>
<p>This product includes software developed by The Apache Software
Foundation (<a href="https://www.apache.org/">https://www.apache.org/</a>).</p>
<p><em> <strong>Commons Logging v1.1.1</strong>
<p>This product includes software developed by The Apache Software
Foundation (<a href="https://www.apache.org/">https://www.apache.org/</a>).</p>
<p><em> <strong>Apache MyFaces v1.2.2</strong>
<p>This product includes software developed by The Apache Software
Foundation (<a href="https://www.apache.org/">https://www.apache.org/</a>).</p>
<p><em> <strong>AspectJ Weaver v1.6.10</strong>
<p>This product includes software developed by The Eclipse Software
Foundation (<a href="https://www.eclipse.org/">https://www.eclipse.org/</a>).</p>
<p><em> <strong>Antlr v3.0.1</strong>
<p>Spring IDE includes a binary version of Antlr v3.0.1 (<a
href="https://www.antlr.org">https://www.antlr.org/</a>).
The source code for Antlr is available from the Antlr download site at
<a href="https://www.antlr.org/download.html">https://www.antlr.org/download.html</a>.
<p>The Antlr license is available at <a
href="https://www.antlr.org/license.html">https://www.antlr.org/license.html</a>.
The license is also reproduced here:</p>
<pre>Copyright (c) 2003-2007, Terence Parr
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this list of conditions
and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this list of conditions
and the following disclaimer in the documentation and/or other materials provided with the distribution.
Neither the name of the author nor the names of its contributors may be used to endorse or promote
products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</pre></p>
<p><em> <strong>ASM v2.2.3</strong>
<p>Spring IDE includes a binary version of ASM v2.2.3 (<a
href="https://asm.objectweb.org/index.html">https://asm.objectweb.org/</a>).
The source code for ASM is available from the ObjectWeb download site at
<a href="https://asm.objectweb.org/download/">https://asm.objectweb.org/download/</a>.
<p>The ASM license is available at <a
href="https://asm.objectweb.org/license.html">https://asm.objectweb.org/license.html</a>.
The license is also reproduced here:</p>
<pre>Copyright (c) 2000-2005 INRIA, France Telecom
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holders nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
THE POSSIBILITY OF SUCH DAMAGE.
</pre></p>
<p><em> <strong>backport-util-concurrent 3.1.0</strong>
<p>Spring IDE includes a binary version of backport-util-concurrent v3.1.0 (<a
href="https://dcl.mathcs.emory.edu/util/backport-util-concurrent/">https://dcl.mathcs.emory.edu/util/backport-util-concurrent/</a>).
The source code for backport-util-concurrent is available from the download site at
<a href="https://dcl.mathcs.emory.edu/util/backport-util-concurrent/dist/">https://dcl.mathcs.emory.edu/util/backport-util-concurrent/dist/</a>.
<p>The backport-util-concurrent license is available at <a
href="https://creativecommons.org/licenses/publicdomain">https://creativecommons.org/licenses/publicdomain</a>.
The license is also reproduced here:</p>
<pre><em>Copyright-Only Dedication (based on United States law) or Public Domain Certification</em>
The person or persons who have associated work with this document (the
"Dedicator" or "Certifier") hereby either (a) certifies that, to the
best of his knowledge, the work of authorship identified is in the public
domain of the country from which the work is published, or (b) hereby
dedicates whatever copyright the dedicators holds in the work of authorship
identified below (the "Work") to the public domain. A certifier, moreover,
dedicates any copyright interest he may have in the associated work, and
for these purposes, is described as a "dedicator" below.
A certifier has taken reasonable steps to verify the copyright status of
this work. Certifier recognizes that his good faith efforts may not shield
him from liability if in fact the work certified is not in the public domain.
Dedicator makes this dedication for the benefit of the public at large and
to the detriment of the Dedicator's heirs and successors. Dedicator intends
this dedication to be an overt act of relinquishment in perpetuity of all
present and future rights under copyright law, whether vested or contingent,
in the Work. Dedicator understands that such relinquishment of all rights
includes the relinquishment of all rights to enforce (by lawsuit or otherwise)
those copyrights in the Work.
Dedicator recognizes that, once placed in the public domain, the Work may be
freely reproduced, distributed, transmitted, used, modified, built upon, or
otherwise exploited by anyone for any purpose, commercial or non-commercial,
and in any way, including by methods that have not yet been invented or
conceived.
</pre></p>
<p><em> <strong>AOP Alliance</strong>
<p>LICENCE: all the source code provided by AOP Alliance is Public Domain.
</p>
<p><em> <strong>javax.el v1.0.0</strong>
<p>This product includes software distributed by Sun (meanwhile Oracle) under CDDL license
(<a href="https://www.sun.com/cddl/cddl.html">https://www.sun.com/cddl/cddl.html</a>).</p>
<p><em> <strong>javax.jms v1.1.0</strong>
<p>This product includes software distributed by Sun (meanwhile Oracle) under CDDL license
(<a href="https://www.sun.com/cddl/cddl.html">https://www.sun.com/cddl/cddl.html</a>).</p>
<p><em> <strong>javax.servlet v2.5.0</strong>
<p>This product includes software distributed by Sun (meanwhile Oracle) under CDDL license
(<a href="https://www.sun.com/cddl/cddl.html">https://www.sun.com/cddl/cddl.html</a>).</p>
<p><em> <strong>javax.servlet.jsp v2.1.0</strong>
<p>This product includes software distributed by Sun (meanwhile Oracle) under CDDL license
(<a href="https://www.sun.com/cddl/cddl.html">https://www.sun.com/cddl/cddl.html</a>).</p>
<p><em> <strong>javax.servlet.jsp.jstl v1.2.0</strong>
<p>This product includes software distributed by Sun (meanwhile Oracle) under CDDL license
(<a href="https://www.sun.com/cddl/cddl.html">https://www.sun.com/cddl/cddl.html</a>).</p>
<p><em> <strong>javax.activation v1.1.0</strong>
<p>This product includes software distributed by Sun (meanwhile Oracle) under CDDL license
(<a href="https://www.sun.com/cddl/cddl.html">https://www.sun.com/cddl/cddl.html</a>).</p>
<p><em> <strong>javax.annotation v1.0.0</strong>
<p>This product includes software distributed by Sun (meanwhile Oracle) under CDDL license
(<a href="https://www.sun.com/cddl/cddl.html">https://www.sun.com/cddl/cddl.html</a>).</p>
<p><em> <strong>javax.mail v1.4.0</strong>
<p>This product includes software distributed by Sun (meanwhile Oracle) under CDDL license
(<a href="https://www.sun.com/cddl/cddl.html">https://www.sun.com/cddl/cddl.html</a>).</p>
<p><em> <strong>javax.xml.stream v1.0.1</strong>
<p>This product includes software distributed by Sun (meanwhile Oracle) under CDDL license
(<a href="https://www.sun.com/cddl/cddl.html">https://www.sun.com/cddl/cddl.html</a>).</p>
<p><em> <strong>javax.xml.ws v2.1.0</strong>
<p>This product includes software distributed by Sun (meanwhile Oracle) under CDDL license
(<a href="https://www.sun.com/cddl/cddl.html">https://www.sun.com/cddl/cddl.html</a>).</p>
</em></p>
<address>Copyright (c) 2005, 2011 Spring IDE Developers</address>
</body>
</html>

View File

@@ -0,0 +1,6 @@
source.. = src/
output.. = bin/
bin.includes = META-INF/,\
.,\
plugin.properties,\
about.html

View File

@@ -0,0 +1,2 @@
providerName = Pivotal Inc.
pluginName = Editor Support Components

View File

@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot.ide</groupId>
<artifactId>org.springframework.boot.ide</artifactId>
<version>4.8.1-SNAPSHOT</version>
<relativePath>../../eclipse-distribution/pom.xml</relativePath>
</parent>
<groupId>org.springframework.ide.eclipse</groupId>
<artifactId>org.springframework.ide.eclipse.editor.support</artifactId>
<packaging>eclipse-plugin</packaging>
<build>
<plugins>
<plugin>
<groupId>org.eclipse.tycho</groupId>
<artifactId>target-platform-configuration</artifactId>
<version>${tycho-version}</version>
<configuration>
<resolver>p2</resolver>
<pomDependencies>ignore</pomDependencies>
</configuration>
</plugin>
<plugin>
<groupId>org.eclipse.tycho</groupId>
<artifactId>tycho-compiler-plugin</artifactId>
<version>${tycho-version}</version>
</plugin>
<plugin>
<groupId>org.eclipse.tycho</groupId>
<artifactId>tycho-source-plugin</artifactId>
<version>${tycho-version}</version>
<executions>
<execution>
<id>plugin-source</id>
<phase>generate-sources</phase>
<goals>
<goal>plugin-source</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.eclipse.tycho</groupId>
<artifactId>tycho-p2-plugin</artifactId>
<version>${tycho-version}</version>
<executions>
<execution>
<id>second-generate-p2-metadata</id>
<goals>
<goal>p2-metadata</goal>
</goals>
<phase>verify</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,66 @@
/*******************************************************************************
* Copyright (c) 2016 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.osgi.framework.BundleContext;
import org.springsource.ide.eclipse.commons.livexp.util.ExceptionUtil;
/**
* The activator class controls the plug-in life cycle
*/
public class EditorSupportActivator extends AbstractUIPlugin {
// The plug-in ID
public static final String PLUGIN_ID = "org.springframework.ide.eclipse.yaml.editor"; //$NON-NLS-1$
// The shared instance
private static EditorSupportActivator plugin;
/**
* The constructor
*/
public EditorSupportActivator() {
}
/*
* (non-Javadoc)
* @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext)
*/
public void start(BundleContext context) throws Exception {
super.start(context);
plugin = this;
}
/*
* (non-Javadoc)
* @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext)
*/
public void stop(BundleContext context) throws Exception {
plugin = null;
super.stop(context);
}
/**
* Returns the shared instance
*
* @return the shared instance
*/
public static EditorSupportActivator getDefault() {
return plugin;
}
public static void log(Throwable e) {
getDefault().getLog().log(ExceptionUtil.status(e));
}
}

View File

@@ -0,0 +1,33 @@
/*******************************************************************************
* Copyright (c) 2015 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support;
import org.eclipse.jface.text.reconciler.IReconcilingStrategy;
import org.eclipse.jface.text.reconciler.MonoReconciler;
/**
* We unforytunately must subclass this just to make it possible to call non
* public method 'forceReconcile'.
* <p>
* We need this to be able to force a reconcile at certain times (e.g. when
* some underlying data defining the schema has changed).
*/
public class ForceableReconciler extends MonoReconciler {
public ForceableReconciler(IReconcilingStrategy strategy) {
super(strategy, false);
}
public void forceReconcile() {
super.forceReconciling();
}
}

View File

@@ -0,0 +1,333 @@
/*******************************************************************************
* Copyright (c) 2014, 2017 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.completions;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IInformationControlCreator;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.contentassist.ICompletionProposalExtension3;
import org.eclipse.jface.text.contentassist.ICompletionProposalExtension4;
import org.eclipse.jface.text.contentassist.ICompletionProposalExtension5;
import org.eclipse.jface.text.contentassist.ICompletionProposalExtension6;
import org.eclipse.jface.text.contentassist.ICompletionProposalSorter;
import org.eclipse.jface.text.contentassist.IContextInformation;
import org.eclipse.jface.viewers.StyledString;
import org.eclipse.jface.viewers.StyledString.Styler;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.TextStyle;
import org.springframework.ide.eclipse.editor.support.EditorSupportActivator;
import org.springframework.ide.eclipse.editor.support.hover.HoverInfo;
import org.springframework.ide.eclipse.editor.support.hover.HoverInformationControlCreator;
import org.springframework.ide.eclipse.editor.support.hover.YPropertyHoverInfo;
import org.springframework.ide.eclipse.editor.support.util.ColorManager;
import org.springframework.ide.eclipse.editor.support.yaml.completions.AbstractPropertyProposal;
import org.springframework.ide.eclipse.editor.support.yaml.schema.YType;
import org.springframework.ide.eclipse.editor.support.yaml.schema.YTypeUtil;
import org.springframework.ide.eclipse.editor.support.yaml.schema.YTypedProperty;
/**
* @author Kris De Volder
*/
public class CompletionFactory {
public static final Styler HIGHLIGHT = new Styler() {
public void applyStyles(TextStyle textStyle) {
textStyle.foreground = ColorManager.getInstance().getColor(ColorManager.CYAN);
};
};
public static final Styler DEPRECATE = new Styler() {
public void applyStyles(TextStyle textStyle) {
textStyle.strikeout = true;
};
};
public static final Styler DEEMPHASIZE = new Styler() {
public void applyStyles(TextStyle textStyle) {
textStyle.foreground = ColorManager.getInstance().getColor(ColorManager.GREY);
};
};
public static final Styler NULL_STYLER = new Styler() {
public void applyStyles(TextStyle textStyle) {
};
};
public static Styler compose(final Styler s1, final Styler s2) {
if (s1==NULL_STYLER) {
return s2;
} else if (s2==NULL_STYLER) {
return s1;
} else {
return new Styler() {
@Override
public void applyStyles(TextStyle textStyle) {
s1.applyStyles(textStyle);
s2.applyStyles(textStyle);
}
};
}
}
public static final CompletionFactory DEFAULT = new CompletionFactory();
public ScoreableProposal simpleProposal(String name, String pattern, int sortingOrder, ProposalApplier applier, HoverInfo info) {
return simpleProposal(name, pattern, -(1.0+sortingOrder), applier, info);
}
public ScoreableProposal simpleProposal(String name, String pattern, double score, ProposalApplier applier, HoverInfo info) {
return new SimpleProposal(name, pattern, score, applier, info);
}
public ScoreableProposal simpleProposal(String name, String pattern, String label, double score, ProposalApplier applier, HoverInfo info) {
return new SimpleProposal(name, pattern, label, score, applier, info);
}
public static abstract class ScoreableProposal implements ICompletionProposal, ICompletionProposalExtension3, ICompletionProposalExtension4, ICompletionProposalExtension5, ICompletionProposalExtension6 {
private static final double DEEMP_VALUE = 100000; // should be large enough to move deemphasized stuff to bottom of list.
private double deemphasizedBy = 0.0;
public abstract double getBaseScore();
public final double getScore() {
return getBaseScore() - deemphasizedBy;
}
public ScoreableProposal deemphasize() {
deemphasizedBy+= DEEMP_VALUE;
return this;
}
public boolean isDeemphasized() {
return deemphasizedBy > 0;
}
@Override
public boolean isAutoInsertable() {
return !isDeemphasized();
}
public StyledString getStyledDisplayString() {
StyledString result = new StyledString();
highlightPattern(getHighlightPattern(), getBaseDisplayString(), result);
return result;
}
private void highlightPattern(String pattern, String data, StyledString result) {
Styler highlightStyle = CompletionFactory.HIGHLIGHT;
Styler plainStyle = isDeemphasized()?CompletionFactory.DEEMPHASIZE:CompletionFactory.NULL_STYLER;
if (isDeprecated()) {
highlightStyle = CompletionFactory.compose(highlightStyle, CompletionFactory.DEPRECATE);
plainStyle = CompletionFactory.compose(plainStyle, CompletionFactory.DEPRECATE);
}
if (!StringUtils.isBlank(pattern)) {
int dataPos = 0; int dataLen = data.length();
int patternPos = 0; int patternLen = pattern.length();
while (dataPos<dataLen && patternPos<patternLen) {
int pChar = pattern.charAt(patternPos++);
int highlightPos = data.indexOf(pChar, dataPos);
if (dataPos<highlightPos) {
result.append(data.substring(dataPos, highlightPos), plainStyle);
}
result.append(data.charAt(highlightPos), highlightStyle);
dataPos = highlightPos+1;
}
if (dataPos<dataLen) {
result.append(data.substring(dataPos), plainStyle);
}
} else { //no pattern to highlight
result.append(data, plainStyle);
}
}
protected abstract boolean isDeprecated();
protected abstract String getHighlightPattern();
protected abstract String getBaseDisplayString();
@Override
public IInformationControlCreator getInformationControlCreator() {
return new HoverInformationControlCreator("F2 for focus");
}
@Override
public String getAdditionalProposalInfo() {
HoverInfo hoverInfo = getAdditionalProposalInfo(new NullProgressMonitor());
if (hoverInfo!=null) {
return hoverInfo.getHtml();
}
return null;
}
@Override
public abstract HoverInfo getAdditionalProposalInfo(IProgressMonitor monitor);
@Override
public CharSequence getPrefixCompletionText(IDocument document, int completionOffset) {
return null;
}
@Override
public int getPrefixCompletionStart(IDocument document, int completionOffset) {
return completionOffset;
}
}
private static class SimpleProposal extends ScoreableProposal {
private String value;
private String label;
private ProposalApplier applier;
private double score;
private String pattern;
private HoverInfo hoverInfo;
public SimpleProposal(String value, String pattern, double score, ProposalApplier applier, HoverInfo info) {
this(value, pattern, value, score, applier, info);
}
public SimpleProposal(String value, String pattern, String label, double score, ProposalApplier applier, HoverInfo info) {
this.score = score;
this.value = value;
this.applier = applier;
this.pattern = pattern;
this.hoverInfo = info;
this.label = label;
}
@Override
public void apply(IDocument doc) {
try {
applier.apply(doc);
} catch (Exception e) {
EditorSupportActivator.log(e);
}
}
@Override
public Point getSelection(IDocument doc) {
try {
return applier.getSelection(doc);
} catch (Exception e) {
EditorSupportActivator.log(e);
}
return null;
}
@Override
public HoverInfo getAdditionalProposalInfo(IProgressMonitor monitor) {
return hoverInfo;
}
@Override
public String getDisplayString() {
return label.toString();
}
@Override
public Image getImage() {
return null;
}
@Override
public IContextInformation getContextInformation() {
return null;
}
@Override
public double getBaseScore() {
return score;
}
@Override
protected boolean isDeprecated() {
return false;
}
@Override
protected String getHighlightPattern() {
return pattern;
}
@Override
protected String getBaseDisplayString() {
return label.toString();
}
}
/**
* A sorter suitable for sorting proposals created by this factory
*/
public static final ICompletionProposalSorter SORTER = new ICompletionProposalSorter() {
public int compare(ICompletionProposal p1, ICompletionProposal p2) {
if (p1 instanceof ScoreableProposal && p2 instanceof ScoreableProposal) {
double s1 = ((ScoreableProposal)p1).getScore();
double s2 = ((ScoreableProposal)p2).getScore();
if (s1==s2) {
String name1 = ((ScoreableProposal)p1).getDisplayString();
String name2 = ((ScoreableProposal)p2).getDisplayString();
return name1.compareTo(name2);
} else {
return Double.compare(s2, s1);
}
}
return 0;
}
};
public ScoreableProposal beanProperty(IDocument doc, final String contextProperty, final YType contextType, final String pattern, final YTypedProperty p, final double score, ProposalApplier applier, final YTypeUtil typeUtil) {
return new AbstractPropertyProposal(doc, applier) {
@Override
public double getBaseScore() {
return score;
}
@Override
protected String niceTypeName(YType type) {
return typeUtil.niceTypeName(type);
}
@Override
protected YType getType() {
return p.getType();
}
@Override
protected String getHighlightPattern() {
return pattern;
}
@Override
protected String getBaseDisplayString() {
return p.getName();
}
@Override
public HoverInfo getAdditionalProposalInfo(IProgressMonitor monitor) {
return new YPropertyHoverInfo(contextProperty, contextType, p);
}
};
}
public ScoreableProposal valueProposal(String value, String pattern, YType yType, double score, ProposalApplier applier, HoverInfo info) {
return simpleProposal(value, pattern, score, applier, info);
}
public ScoreableProposal valueProposal(String value, String pattern, String label, YType yType, double score, ProposalApplier applier, HoverInfo info) {
return simpleProposal(value, pattern, label, score, applier, info);
}
public ScoreableProposal valueProposal(String value, String pattern, YType type, int order, ProposalApplier applier, HoverInfo info) {
return valueProposal(value, pattern, type, -(1.0+order), applier, info);
}
}

View File

@@ -0,0 +1,324 @@
/*******************************************************************************
* Copyright (c) 2015 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.completions;
import java.util.ArrayList;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.swt.graphics.Point;
import org.eclipse.text.edits.TextEdit;
/**
* Helper to make it easier to create composite modifications to IDocument.
* <p>
* It allows building up a sequence of edits which are all expressed in terms of
* offsets in the unmodified document. (So, when computing edits based on a
* some kind of AST its is not necessary to recompute the AST or update its
* position information after each small modification).
* <p>
* Similar functionality to Eclipse's {@link TextEdit} but unlike {@link TextEdit}
* it is not as finicky with respect to overlapping edits. We consider the
* order in which edits are created meaningful and give a logical semantics
* to edits that 'overlap'.
* <p>
* Also, each edit affects the cursor position placing it at the end of
* that edit. This will mostly do what you would want it to, provided that
* you save the edit where you want the cursor to end-up at for last.
*
* @author Kris De Volder
*/
public class DocumentEdits implements ProposalApplier {
// Note: for small number of edits this implementation is okay.
// for large number of edits it is potentially slow because of the
// way it transforms edit coordinates (a growing chain of
// OffsetTransformer is created so every extra edit added
// will take O(n) to preform the transform on its coordinates.
// So applying 'n' edits is O(n^2).
//
// A smarter way of doing this is possible. Here's a possible idea:
//
// For simplicity sake assume that all edits are 'independent' (i.e.
// changing their executing order doesn't matter.
//
// It is advantageous to sort the edits by position and execute them
// high to low because... we can then guarantee that the transform
// function that will apply to each edit does nothing on the coordinates
// that it cares about (since all prior edits only affect higher offets)
//
// Unfortunately the simplifying assumption does not allways hold.
// There are two problems:
//
// 1) updating the selection is order dependent.
// => this can be solved by observing that only the last
// edit operation need update the selection since it cancels
// all prior selections.
// => Mark the last operation with a 'flag' 'setSelection=true'
// and do not update the selection in any other operations.
//
// 2) some edits may not be independent
// => When the edits are sorted in descending order based on their 'end'
// coordinate them 'conflicting' edits should be adjacent and we can
// 'group them' together into 'cluster' where we can preserve their
// relative execution order.
// => While executing a 'cluster' we shall keep track of the offset
// transform function just like the current implementation does.
// => When the cluster of 'conflicting' operations has been dealt with
// the offset transform function no longer matters for the
// remaining edits who's offesets are all strictly 'smaller'.
// Thus the trasnform function can be discarded.
//
// Assuming most edits are independent and only a few of them conflict, then
// this algorithm can provide equivalent functinonality to the current one
// but for an 'average' performance which is O(n*log(n))
// Of course worst-case is still O(n^2) but we wouldn't expect to hit that case
// assuming we mostly have lots of small edits to disjoint sections of the document.
//
// So... edit operations could be sorted based on their position
// and executed in decreasing order of their 'start'.
//
// The tricky part would be to preserve the order-dependent semantics.
private class Insertion extends Edit {
private int offset;
private String text;
public Insertion(int offset, String insert) {
this.offset = offset;
this.text = insert;
}
@Override
void apply(DocumentState doc) throws BadLocationException {
doc.insert(offset, text);
}
@Override
public String toString() {
return "ins("+text+"@"+offset+")";
}
}
private abstract class Edit {
abstract void apply(DocumentState doc) throws BadLocationException;
public abstract String toString();
}
private class Deletion extends Edit {
private int start;
private int end;
public Deletion(int start, int end) {
this.start = start;
this.end = end;
}
@Override
void apply(DocumentState doc) throws BadLocationException {
doc.delete(start, end);
}
@Override
public String toString() {
return "del("+start+"->"+end+")";
}
}
private interface OffsetTransformer {
int trasform(int offset);
}
private static final OffsetTransformer NULL_TRANSFORM = new OffsetTransformer() {
public int trasform(int offset) {
return offset;
}
};
/**
* DocumentState provides methods to modify a document, its methods accept
* offsets expressed relative to the original document contents and keeps track
* of a OffsetTransformer that maps them to offsets in the current document.
*/
private static class DocumentState {
private IDocument doc; //may be null, in which case no actual modifications are performed
private OffsetTransformer org2new = NULL_TRANSFORM;
private int selection = -1; //-1 Means no edits where applied that change selection so
// the current selection is unknown
public DocumentState(IDocument doc) {
this.doc = doc;
}
public void insert(int start, final String text) throws BadLocationException {
final int tStart = org2new.trasform(start);
if (!text.isEmpty()) {
if (doc!=null) {
doc.replace(tStart, 0, text);
}
final OffsetTransformer parent = org2new;
org2new = new OffsetTransformer() {
public int trasform(int org) {
int tOffset = parent.trasform(org);
if (tOffset<tStart) {
return tOffset;
} else {
return tOffset + text.length();
}
}
};
}
selection = tStart+text.length();
}
public void delete(final int start, final int end) throws BadLocationException {
final int tStart = org2new.trasform(start);
if (end>start) { // skip work for 'delete nothing' op
final int tEnd = org2new.trasform(end);
if (tEnd>tStart) { // skip work for 'delete nothing' op
if (doc!=null) {
doc.replace(tStart, tEnd-tStart, "");
}
final OffsetTransformer parent = org2new;
org2new = new OffsetTransformer() {
public int trasform(int org) {
int tOffset = parent.trasform(org);
if (tOffset<=tStart) {
return tOffset;
} else if (tOffset>=tEnd) {
return tOffset - tEnd + tStart;
} else {
return start;
}
}
};
}
}
selection = tStart;
}
@Override
public String toString() {
if (doc==null) {
return super.toString();
}
StringBuilder buf = new StringBuilder();
buf.append("DocumentState(\n");
buf.append(doc.get()+"\n");
buf.append(")\n");
return buf.toString();
}
}
private ArrayList<Edit> edits = new ArrayList<Edit>();
private IDocument doc;
public DocumentEdits(IDocument doc) {
this.doc = doc;
}
public void delete(int start, int end) {
Assert.isLegal(start<=end);
edits.add(new Deletion(start, end));
}
public void delete(int offset, String text) {
delete(offset, offset+text.length());
}
public void insert(int offset, String insert) {
edits.add(new Insertion(offset, insert));
}
@Override
public Point getSelection(IDocument doc) throws Exception {
DocumentState selectionState = new DocumentState(null);
for (Edit edit : edits) {
edit.apply(selectionState);
}
if (selectionState.selection>=0) {
return new Point(selectionState.selection, 0);
}
return null;
}
@Override
public void apply(IDocument _doc) throws Exception {
DocumentState doc = new DocumentState(_doc);
for (Edit edit : edits) {
edit.apply(doc);
}
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append("DocumentModifier(\n");
for (Edit edit : edits) {
buf.append(" "+edit);
}
buf.append(")\n");
return buf.toString();
}
public void moveCursorTo(int newCursor) {
insert(newCursor, "");
}
public void deleteLineBackwardAtOffset(int offset) throws Exception {
int line = doc.getLineOfOffset(offset);
deleteLineBackward(line);
}
/**
* Deletes the line of text with given line number, including either the following or
* preceding newline. If there is a choice between the preceding or following newline,
* the preceding newline is deleted. This will leave the cursor at the end of
* the preceding line.
* <p>
* Note: a similar operation 'deleteLineForward' could be implemented prefering to
* delete the following newline. This would be equivalent except that it will leave the
* cursor at the start of the following line.
*/
public void deleteLineBackward(int lineNumber) throws BadLocationException {
IRegion line = doc.getLineInformation(lineNumber);
int startOfDeletion;
int endOfDeletion;
if (lineNumber>0) {
IRegion previousLine = doc.getLineInformation(lineNumber-1);
startOfDeletion = endOf(previousLine);
endOfDeletion = endOf(line);
} else if (lineNumber<doc.getNumberOfLines()-1) {
IRegion nextLine = doc.getLineInformation(lineNumber+1);
startOfDeletion = line.getOffset();
endOfDeletion = nextLine.getOffset();
} else {
startOfDeletion = line.getOffset();
endOfDeletion = endOf(line);
}
delete(startOfDeletion, endOfDeletion);
}
private int endOf(IRegion line) {
return line.getOffset()+line.getLength();
}
public void replace(int start, int end, String newText) {
delete(start, end);
insert(start, newText);
}
}

View File

@@ -0,0 +1,25 @@
/*******************************************************************************
* Copyright (c) 2015, 2016 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.completions;
import java.util.Collection;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
/**
* @author Kris De Volder
*/
public interface ICompletionEngine {
Collection<ICompletionProposal> getCompletions(IDocument document, int offset) throws Exception;
}

View File

@@ -0,0 +1,36 @@
/*******************************************************************************
* Copyright (c) 2015 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.completions;
import org.eclipse.jface.text.IDocument;
import org.eclipse.swt.graphics.Point;
/**
* Interface that represents the methods that one needs to implement in order
* to define how content assist proposal is applied to a IDocument
*
* @author Kris De Volder
*/
public interface ProposalApplier {
/**
* {@link ProposalApplier} that does nothing whatsoever.
*/
static ProposalApplier NULL = new ProposalApplier() {
@Override public Point getSelection(IDocument document) { return null; }
@Override public void apply(IDocument doc) {}
@Override public String toString() { return "NULL";};
};
Point getSelection(IDocument document) throws Exception;
void apply(IDocument doc) throws Exception;
}

View File

@@ -0,0 +1,97 @@
/*******************************************************************************
* Copyright (c) 2005, 2008, 2016 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
* Genady Beryozkin, me@genady.org - #getSuggestions implementation copied from HippieCompleteAction
* Kris De Volder - Copied and modified HippieCompletionProcessor to become 'SpringPropertiesCompletionProcessor'.
* - later on modified some more to make it more reusable as a 'general' wrapper around
* a ICompletionEngine
********************************************************************************/
package org.springframework.ide.eclipse.editor.support.completions;
import java.util.Collection;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.contentassist.IContentAssistProcessor;
import org.eclipse.jface.text.contentassist.IContextInformation;
import org.eclipse.jface.text.contentassist.IContextInformationValidator;
import org.springframework.ide.eclipse.editor.support.EditorSupportActivator;
/**
* Wraps a {@link ICompletionEngine} so it can be use as a {@link IContentAssistProcessor}
*
* @author Kris De Volder
*/
public class ProposalProcessor implements IContentAssistProcessor {
private static final ICompletionProposal[] NO_PROPOSALS= new ICompletionProposal[0];
private static final IContextInformation[] NO_CONTEXTS= new IContextInformation[0];
public static final char[] AUTO_ACTIVATION_CHARS = ".ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".toCharArray();
private final ICompletionEngine fEngine;
public ProposalProcessor(ICompletionEngine engine) {
this.fEngine = engine;
}
/*
* @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#computeCompletionProposals(org.eclipse.jface.text.ITextViewer, int)
*/
public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset) {
try {
Collection<ICompletionProposal> proposals = fEngine.getCompletions(viewer.getDocument(), offset);
if (proposals==null || proposals.isEmpty()) {
return NO_PROPOSALS;
} else {
return proposals.toArray(new ICompletionProposal[proposals.size()]);
}
} catch (Exception e) {
EditorSupportActivator.log(e);
return NO_PROPOSALS;
}
}
/*
* @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#computeContextInformation(org.eclipse.jface.text.ITextViewer, int)
*/
public IContextInformation[] computeContextInformation(ITextViewer viewer, int offset) {
// no context informations for hippie completions
return NO_CONTEXTS;
}
/*
* @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#getCompletionProposalAutoActivationCharacters()
*/
public char[] getCompletionProposalAutoActivationCharacters() {
return AUTO_ACTIVATION_CHARS;
}
/*
* @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#getContextInformationAutoActivationCharacters()
*/
public char[] getContextInformationAutoActivationCharacters() {
return null;
}
/*
* @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#getContextInformationValidator()
*/
public IContextInformationValidator getContextInformationValidator() {
return null;
}
/*
* @see org.eclipse.jface.text.contentassist.ICompletionProposalComputer#getErrorMessage()
*/
public String getErrorMessage() {
return null; // no custom error message
}
}

View File

@@ -0,0 +1,67 @@
/*******************************************************************************
* Copyright (c) 2016 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.hover;
import static org.springframework.ide.eclipse.editor.support.util.HtmlSnippet.italic;
import static org.springframework.ide.eclipse.editor.support.util.HtmlSnippet.raw;
import static org.springframework.ide.eclipse.editor.support.util.HtmlSnippet.text;
import java.io.InputStream;
import javax.inject.Provider;
import org.springframework.ide.eclipse.editor.support.EditorSupportActivator;
import org.springframework.ide.eclipse.editor.support.util.HtmlSnippet;
import org.springsource.ide.eclipse.commons.frameworks.core.util.IOUtil;
/**
* Static methods and convenience constants for creating some 'description providers'.
*
* @author Kris De Volder
*/
public class DescriptionProviders {
public static final Provider<HtmlSnippet> NO_DESCRIPTION = snippet(italic(text("no description")));
public static Provider<HtmlSnippet> snippet(final HtmlSnippet snippet) {
return new Provider<HtmlSnippet>() {
@Override
public String toString() {
return snippet.toString();
}
@Override
public HtmlSnippet get() {
return snippet;
}
};
}
public static Provider<HtmlSnippet> fromClasspath(final Class<?> klass, final String resourcePath) {
return new Provider<HtmlSnippet>() {
@Override
public String toString() {
return "HtmlSnippetFromClassPth(class="+klass.getSimpleName()+", "+resourcePath+")";
}
@Override
public HtmlSnippet get() {
try {
InputStream stream = klass.getResourceAsStream(resourcePath);
if (stream!=null) {
return raw(IOUtil.toString(stream));
}
} catch (Exception e) {
EditorSupportActivator.log(e);
}
return NO_DESCRIPTION.get();
}
};
}
}

View File

@@ -0,0 +1,168 @@
/*******************************************************************************
* Copyright (c) 2015, 2016 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.hover;
import java.net.URI;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jface.internal.text.html.BrowserInformationControlInput;
import org.springframework.ide.eclipse.editor.support.util.HtmlBuffer;
import org.springframework.ide.eclipse.editor.support.util.HtmlUtil;
import com.google.common.collect.ImmutableList;
@SuppressWarnings("restriction")
public abstract class HoverInfo extends BrowserInformationControlInput {
/**
* Fake host name that is used in 'action link' urls so that we can
* recognize them as such.
*/
private static final String ACTION_HOST = "action";
/**
* Registry for 'action link ids' so that we can map clicked action link
* to a Runnable to execute it.
*/
private Map<String, Runnable> actions = null; //only created if used
/**
* Cached html representation of this info. So the info is computed only once.
*/
private String html;
public HoverInfo() {
super(null);
}
@Override
public Object getInputElement() {
return this;
}
@Override
public String getInputName() {
return "";
}
/**
* Fetches an html-formatted represetation of this element. Subclasses should not
* implement this. Instead they should override the 'renderAsHtml' method.
*/
public final String getHtml() {
if (html==null) {
html = renderAsHtml();
}
return html;
}
/**
* Subclass must implement this method to format its info as html. Clients should
* not call this method directly, instead they should call 'getHtml' which will return
* a cached copy of what this method computes.
*/
protected abstract String renderAsHtml();
/**
* IJavaElements associated with the hover target. Used by 'open declaration'
* action.
*/
public List<IJavaElement> getJavaElements() {
return ImmutableList.of();
}
public static HoverInfo withText(final String plainText) {
if (plainText!=null) {
return new HoverInfo() {
@Override
public String renderAsHtml() {
return HtmlUtil.text2html(plainText);
}
};
}
return null;
}
/**
* Called by the brower information control when a link is about to be followed. This method
* can then decide to handle the link (if it corresponds to an actionId registered with the
* object. If the link was handled then the linkHanlder should return true to indicate this.
*/
public boolean handleActionLink(String link) {
String actionId = getActionLinkTarget(link);
if (actionId!=null) {
Runnable action = getAction(actionId);
if (action!=null) {
action.run();
}
return true;
}
return false;
}
private Runnable getAction(String actionId) {
if (actions!=null) {
return actions.get(actionId);
}
return null;
}
/**
* Creates an 'action' link and adds it to the html buffer. When the user clicks the given
* link then the provided runnable is to be executed.
*/
public void actionLink(HtmlBuffer html, String displayString, Runnable runnable) {
String actionId = registerAction(runnable);
html.raw("<a href=\"http://"+ACTION_HOST+"/");
html.url(actionId);
html.raw("\">");
html.text(displayString);
html.raw("</a>");
}
private synchronized String registerAction(Runnable runnable) {
if (actions==null) {
actions = new HashMap<>();
}
String actionId = ""+actions.size();
actions.put(actionId, runnable);
return actionId;
}
/**
* Extract the 'action id' from a url location, if it represents an action link. Otherwise
* returns null.
*/
private String getActionLinkTarget(String location) {
try {
if (location!=null) {
URI uri = new URI(location);
if (ACTION_HOST.equals(uri.getHost())) {
String path = URLDecoder.decode(uri.getPath(), "utf8");
while (path.startsWith("/")) {
path = path.substring(1);
}
return path;
}
}
} catch (Exception e) {
//ignore
}
return null;
}
}

View File

@@ -0,0 +1,25 @@
/*******************************************************************************
* Copyright (c) 2015 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.hover;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
/**
* A simple interface to implement for a component that provides HoverInfo based on
* editor contents.
*
* @author Kris De Volder
*/
public interface HoverInfoProvider {
HoverInfo getHoverInfo(IDocument doc, IRegion r);
IRegion getHoverRegion(IDocument document, int offset);
}

View File

@@ -0,0 +1,111 @@
/*******************************************************************************
* Copyright (c) 2014 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.hover;
import org.eclipse.jface.internal.text.html.BrowserInformationControlInput;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IInformationControlCreator;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextHover;
import org.eclipse.jface.text.ITextHoverExtension;
import org.eclipse.jface.text.ITextHoverExtension2;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.ui.editors.text.EditorsUI;
import org.springframework.ide.eclipse.editor.support.util.HtmlUtil;
/**
* An implementation of Eclipse's {@link ITextHover} and some of its clunky 'extension'
* interfaces, that wraps a simple 'HoverInfoProvider' (so that implementing something
* to provide hover infos based on an editor's contents doesn't have to be horribly
* complicated).
*
* @author Kris De Volder
*/
@SuppressWarnings("restriction")
public class HoverInfoTextHover implements ITextHover, ITextHoverExtension, ITextHoverExtension2 {
private HoverInfoProvider _hovers;
private ITextHover delegate;
/**
* Create a {@link HoverInfoTextHover} based on a given {@link HoverInfoProvider}. It is also possible
* to provide an optioanl {@link ITextHover} delegate. This 'wrapper' will try to delegate hover info
* requests to the delegate and combine its results with our own {@link HoverInfoProvider} in some
* sensible way. Note however that for this to work the delegate must produce hover informations
* that can be properly displayed by our own {@link HoverInformationControl}, essentially this
* means they must be based on {@link BrowserInformationControlInput}. If that is not the
* case then our {@link HoverInformationControl} will not be able to display them correctly.
*/
public HoverInfoTextHover(ISourceViewer sourceViewer, HoverInfoProvider hoverInfoProvider, ITextHover delegate) {
this._hovers = hoverInfoProvider;
this.delegate = delegate;
}
public IRegion getHoverRegion(ITextViewer tv, int offset) {
//Note that we ask the 'delegate' first. This is because it handles info about
// error markers from annotations model. And this info should 'override'
// information about property
if (delegate!=null) {
IRegion r = delegate.getHoverRegion(tv, offset);
if (r!=null) {
return r;
}
}
return hovers_getHoverRegion(tv.getDocument(), offset);
}
public String getHoverInfo(ITextViewer tv, IRegion r) {
String s = delegate.getHoverInfo(tv, r);
if (s!=null) {
return HtmlUtil.text2html(s);
}
return hovers_getHoverInfo(tv.getDocument(), r).getHtml();
}
@Override
public Object getHoverInfo2(ITextViewer tv, IRegion r) {
if (delegate instanceof ITextHoverExtension2) {
Object it = ((ITextHoverExtension2) delegate).getHoverInfo2(tv, r);
if (it!=null) {
if (it instanceof String) {
return HtmlUtil.text2html((String)it);
} else {
return it;
}
}
} else if (delegate instanceof ITextHover) {
String it = delegate.getHoverInfo(tv, r);
if (it!=null) {
return HtmlUtil.text2html(it);
}
}
return hovers_getHoverInfo(tv.getDocument(), r);
}
@Override
public IInformationControlCreator getHoverControlCreator() {
return new HoverInformationControlCreator(EditorsUI.getTooltipAffordanceString());
}
private IRegion hovers_getHoverRegion(IDocument document, int offset) {
IRegion r = _hovers.getHoverRegion(document, offset);
// System.out.println("getHRegion("+offset+") => "+r);
return r;
}
private HoverInfo hovers_getHoverInfo(IDocument document, IRegion r) {
HoverInfo result = _hovers.getHoverInfo(document, r);
// System.out.println("getHInfo("+r+") => "+result);
return result;
}
}

View File

@@ -0,0 +1,53 @@
/*******************************************************************************
* Copyright (c) 2015, 2016 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.hover;
import org.eclipse.jface.action.ToolBarManager;
import org.eclipse.jface.internal.text.html.BrowserInformationControl;
import org.eclipse.jface.internal.text.html.BrowserInformationControlInput;
import org.eclipse.swt.browser.LocationEvent;
import org.eclipse.swt.browser.LocationListener;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Shell;
@SuppressWarnings("restriction")
public class HoverInformationControl extends BrowserInformationControl {
public HoverInformationControl(Shell parent, String appearanceJavadocFont, ToolBarManager toolbar) {
super(parent, appearanceJavadocFont, toolbar);
}
public HoverInformationControl(Shell parent, String appearanceJavadocFont, String statusText) {
super(parent, appearanceJavadocFont, statusText);
}
@Override
protected void createContent(Composite parent) {
super.createContent(parent);
addLocationListener(new LocationListener() {
@Override
public void changing(LocationEvent event) {
BrowserInformationControlInput input = getInput();
if (input instanceof HoverInfo) {
if (((HoverInfo) input).handleActionLink(event.location)) {
getShell().close();
event.doit = false;
}
}
}
@Override
public void changed(LocationEvent event) {
}
});
}
}

View File

@@ -0,0 +1,136 @@
/*******************************************************************************
* Copyright (c) 2014, 2016 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.hover;
import java.util.List;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.internal.ui.JavaPluginImages;
import org.eclipse.jdt.ui.JavaUI;
import org.eclipse.jdt.ui.PreferenceConstants;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.ToolBarManager;
import org.eclipse.jface.internal.text.html.BrowserInformationControl;
import org.eclipse.jface.internal.text.html.BrowserInformationControlInput;
import org.eclipse.jface.text.DefaultInformationControl;
import org.eclipse.jface.text.IInformationControl;
import org.eclipse.jface.text.IInformationControlCreator;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Shell;
import org.springframework.ide.eclipse.editor.support.EditorSupportActivator;
/**
* IInformationControlCreator for 'hover information' associated with editor contents. This control is
* used in two different contexts.
*
* - tooltip info shown when hovering over something
* - side view for content assist.
*/
@SuppressWarnings("restriction")
public class HoverInformationControlCreator implements IInformationControlCreator {
/**
* Status text shown in the bottom 'status' area of the control (but it is only shown on the
* non-enriched version of the control)
*/
private String statusText;
/**
* Whether or not a 'enriched' version of the control should be created. Information controls generally
* have two different forms a 'plain' non-resizable form without scrollbars and toolbar and
* an 'enriched' version which may have a toolbar and scrollbars and is shown when the control
* has focus.
*/
private boolean enriched;
public HoverInformationControlCreator(String statusText) {
this(false, statusText);
}
public HoverInformationControlCreator(boolean enriched, String statusText) {
this.enriched = enriched;
this.statusText = statusText;
}
@Override
public IInformationControl createInformationControl(Shell parent) {
if (BrowserInformationControl.isAvailable(parent)) {
if (!enriched) {
return new HoverInformationControl(parent, PreferenceConstants.APPEARANCE_JAVADOC_FONT, statusText) {
@Override
public IInformationControlCreator getInformationPresenterControlCreator() {
return new HoverInformationControlCreator(true, null);
}
};
} else {
ToolBarManager toolbar= new ToolBarManager(SWT.FLAT);
BrowserInformationControl control = new HoverInformationControl(parent, PreferenceConstants.APPEARANCE_JAVADOC_FONT, toolbar) {
@Override
public IInformationControlCreator getInformationPresenterControlCreator() {
return new HoverInformationControlCreator(true, null);
}
};
fillToolbar(toolbar, control);
toolbar.update(true);
return control;
}
}
return new DefaultInformationControl(parent, true);
}
/**
* Add menu items to the toolbar for the enriched version of the control.
*/
private void fillToolbar(ToolBarManager tbm, BrowserInformationControl infoControl) {
tbm.add(new OpenDeclarationAction(infoControl));
}
/**
* Action that opens the current hover input element.
*
* @since 3.4
*/
private static final class OpenDeclarationAction extends Action {
private final BrowserInformationControl fInfoControl;
public OpenDeclarationAction(BrowserInformationControl infoControl) {
fInfoControl= infoControl;
setText("Open Declaration");
JavaPluginImages.setLocalImageDescriptors(this, "goto_input.gif"); //$NON-NLS-1$
}
/*
* @see org.eclipse.jface.action.Action#run()
*/
@Override
public void run() {
try {
BrowserInformationControlInput input = fInfoControl.getInput();
if (input instanceof HoverInfo) {
HoverInfo infoInput= (HoverInfo) input;
if (infoInput!=null) {
List<IJavaElement> elements = infoInput.getJavaElements();
//TODO: This only opens the first element, if there's more than one should offer a choice/
if (!elements.isEmpty()) {
IJavaElement je = elements.get(0);
fInfoControl.notifyDelayedInputChange(null);
fInfoControl.dispose(); //FIXME: should have protocol to hide, rather than dispose
JavaUI.openInEditor(je);
}
}
}
} catch (Exception e) {
EditorSupportActivator.log(e);
}
}
}
}

View File

@@ -0,0 +1,73 @@
/*******************************************************************************
* Copyright (c) 2015, 2017 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.hover;
import org.apache.commons.lang3.StringUtils;
import org.springframework.ide.eclipse.editor.support.util.HtmlBuffer;
import org.springframework.ide.eclipse.editor.support.util.HtmlSnippet;
import org.springframework.ide.eclipse.editor.support.yaml.schema.YType;
import org.springframework.ide.eclipse.editor.support.yaml.schema.YTypedProperty;
/**
* Nicely formatted hover info for a {@link YTypedProperty}
*
* @author Kris De Volder
*/
public class YPropertyHoverInfo extends HoverInfo {
private String contextProperty;
// private YType contextType;
private YTypedProperty prop;
public YPropertyHoverInfo(String contextProperty, YType contextType, YTypedProperty prop) {
this.contextProperty = contextProperty;
// this.contextType = contextType;
this.prop = prop;
}
private String getName() {
return prop.getName();
}
private YType getType() {
return prop.getType();
}
@Override
protected String renderAsHtml() {
HtmlBuffer html = new HtmlBuffer();
if (!StringUtils.isBlank(contextProperty)) {
html.text(contextProperty);
html.text(".");
}
html.raw("<b>");
html.text(getName());
html.raw("</b>");
html.raw("<br>");
YType type = getType();
if (type!=null) {
actionLink(html, type.toString(), null);
}
HtmlSnippet description = getDescription();
if (description!=null) {
html.raw("<br><br>");
html.snippet(description);
}
return html.toString();
}
protected HtmlSnippet getDescription() {
return prop.getDescription();
}
}

View File

@@ -0,0 +1,225 @@
/*******************************************************************************
* Copyright (c) 2015 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.preferences;
import static org.springframework.ide.eclipse.editor.support.preferences.ProblemSeverityPreferencesUtil.*;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ProjectScope;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.preferences.DefaultScope;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.jface.dialogs.ControlEnableState;
import org.eclipse.jface.preference.ComboFieldEditor;
import org.eclipse.jface.preference.FieldEditorPreferencePage;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPreferencePage;
import org.eclipse.ui.IWorkbenchPropertyPage;
import org.eclipse.ui.preferences.ScopedPreferenceStore;
import org.osgi.service.prefs.BackingStoreException;
import org.springframework.ide.eclipse.boot.util.Log;
import org.springframework.ide.eclipse.editor.support.reconcile.ProblemSeverity;
import org.springframework.ide.eclipse.editor.support.reconcile.ProblemType;
import org.springsource.ide.eclipse.commons.livexp.core.LiveExpression;
import org.springsource.ide.eclipse.commons.livexp.core.LiveVariable;
import org.springsource.ide.eclipse.commons.livexp.core.UIValueListener;
/**
* Abstract superclass to easily implement a preference and/or project property page that allows
* configuring problem severities (based on a list of {@link ProblemType}s).
* <p>
* The page will provide a list of pulldowns to choose each problem's severity.
* <p>
* This is meant to be used in conjunction with a {@link PreferencesBasedSeverityProvider}
*
* @author Kris De Volder
*/
public abstract class AbstractProblemSeverityPreferencesPage extends FieldEditorPreferencePage implements IWorkbenchPreferencePage, IWorkbenchPropertyPage {
/**
* Project for a project propertypage, or null for a workspace preference page.
*/
private IProject project;
/**
* The state of the 'enable project specific settings' checkbox.
*/
private LiveVariable<Boolean> enablePreferences = new LiveVariable<>(true);
private static final Comparator<ProblemType> PROBLEM_TYPE_COMPARATOR = new Comparator<ProblemType>() {
public int compare(ProblemType o1, ProblemType o2) {
return o1.getLabel().compareTo(o2.getLabel());
}
};
private static final String[][] SEVERITY_NAMES_AND_VALUES = {
{"Error", ProblemSeverity.ERROR.toString()},
{"Warning", ProblemSeverity.WARNING.toString()},
{"Ignore", ProblemSeverity.IGNORE.toString()}
};
protected AbstractProblemSeverityPreferencesPage() {
super(FieldEditorPreferencePage.GRID);
initializeDefaults();
}
/**
* Ensures that default preference values for all problem types are entered into the
* DefaultScope. Note that this mainly important for the preferences UI which displays the
* defaults from the peferences store. Other consumers of the preference will
* use ProblemType.getDefaultSeverity() when they can not find a default value in the store.
*/
protected void initializeDefaults() {
IEclipsePreferences defaults = DefaultScope.INSTANCE.getNode(getPluginId());
for (ProblemType problemType : getProblemTypes()) {
defaults.put(getPreferenceName(problemType), problemType.getDefaultSeverity().toString());
}
try {
defaults.flush();
} catch (BackingStoreException e) {
Log.log(e);
}
}
@Override
public void init(IWorkbench workbench) {
setPreferenceStore(new ScopedPreferenceStore(InstanceScope.INSTANCE, getPluginId()));
initializeDefaults();
}
@Override
protected final IPreferenceStore doGetPreferenceStore() {
return super.doGetPreferenceStore();
}
@Override
protected void createFieldEditors() {
ProblemType[] problemTypes = getProblemTypes().toArray(new ProblemType[0]);
Arrays.sort(problemTypes, PROBLEM_TYPE_COMPARATOR);
for (ProblemType problemType : problemTypes) {
ComboFieldEditor field = new ComboFieldEditor(
ProblemSeverityPreferencesUtil.getPreferenceName(problemType),
problemType.getLabel(),
SEVERITY_NAMES_AND_VALUES,
getFieldEditorParent()
);
setTooltip(field, problemType.getDescription());
addField(field);
}
}
@Override
protected Control createContents(Composite parent) {
if (isProjectPropertyPage()) {
return createProjectPageContent(parent);
} else {
return super.createContents(parent);
}
}
private Control createProjectPageContent(Composite parent) {
Composite page = new Composite(parent, SWT.NONE);
page.setLayout(new GridLayout());
CheckboxWidget enablePreferencesCheckbox = new CheckboxWidget(page, enablePreferences);
enablePreferencesCheckbox.setText("Enable project-specific settings");
final Control preferencesControl = super.createContents(page);
enablePreferences.addListener(new UIValueListener<Boolean>() {
private ControlEnableState fBlockEnableState;
protected void enablePreferenceContent(boolean enable) {
if (enable) {
if (fBlockEnableState != null) {
fBlockEnableState.restore();
fBlockEnableState = null;
}
}
else {
if (fBlockEnableState == null) {
fBlockEnableState = ControlEnableState.disable(preferencesControl);
}
}
}
@Override
protected void uiGotValue(LiveExpression<Boolean> exp, Boolean enable) {
if (enable!=null) {
enablePreferenceContent(enable);
}
}
});
return page;
}
private boolean isProjectPropertyPage() {
return project!=null;
}
protected abstract List<ProblemType> getProblemTypes();
protected void setTooltip(ComboFieldEditor field, String tooltip) {
field.getLabelControl(getFieldEditorParent()).setToolTipText(tooltip);
}
@Override
public IAdaptable getElement() {
return project;
}
@Override
public void setElement(IAdaptable element) {
project = (IProject)element.getAdapter(IProject.class);
if (project!=null) {
setPreferenceStore(new ScopedPreferenceStore(new ProjectScope(project), getPluginId()));
enablePreferences.setValue(getPreferenceStore().getBoolean(getEnableProjectPreferencesKey()));
}
}
protected abstract String getPluginId();
@Override
public boolean performOk() {
if (isProjectPropertyPage()) {
getPreferenceStore().setValue(getEnableProjectPreferencesKey(), enablePreferences.getValue());
}
return super.performOk();
}
@Override
protected void performDefaults() {
if (isProjectPropertyPage()) {
enablePreferences.setValue(getPreferenceStore().getDefaultBoolean(getEnableProjectPreferencesKey()));
}
super.performDefaults();
}
/**
* Determines the property-key that is used to strore whether project-specific preferences are enabled for
* the properties on this page.
*/
protected abstract String getEnableProjectPreferencesKey();
}

View File

@@ -0,0 +1,61 @@
/*******************************************************************************
* Copyright (c) 2015 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.preferences;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.springsource.ide.eclipse.commons.livexp.core.LiveExpression;
import org.springsource.ide.eclipse.commons.livexp.core.LiveVariable;
import org.springsource.ide.eclipse.commons.livexp.core.UIValueListener;
/**
* Wraps a Checkbox widget, attached to a LiveVariable so the widget selection
* state is mirrored in the LiveVariable and vice-versa.
*
* @author Kris De Volder
*/
public class CheckboxWidget {
public final Button widget;
public CheckboxWidget(Composite parent, final LiveVariable<Boolean> selected) {
widget = new Button(parent, SWT.CHECK);
selected.addListener(new UIValueListener<Boolean>() {
@Override
protected void uiGotValue(LiveExpression<Boolean> exp, Boolean ignore) {
//since uivalue listener may be called 'later' the value may have changed since
// the event. So get its current state.
Boolean value = selected.getValue();
if (value!=null) {
if (widget.isDisposed()) {
selected.removeListener(this);
} else {
widget.setSelection(value);
}
}
}
});
widget.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
selected.setValue(widget.getSelection());
}
});
}
public void setText(String string) {
widget.setText(string);
}
}

View File

@@ -0,0 +1,19 @@
/*******************************************************************************
* Copyright (c) 2015 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.preferences;
public enum EditorType {
PROP, YAML, JAVA;
public String getProblemTypePrefix() {
return this.toString()+"_";
}
}

View File

@@ -0,0 +1,83 @@
/*******************************************************************************
* Copyright (c) 2015 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.preferences;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ProjectScope;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.ui.preferences.ScopedPreferenceStore;
import org.springframework.ide.eclipse.editor.support.reconcile.ProblemSeverity;
import org.springframework.ide.eclipse.editor.support.reconcile.ProblemType;
import org.springframework.ide.eclipse.editor.support.reconcile.SeverityProvider;
/**
* Implementation of {@link SeverityProvider} that determines the severity of a problem
* by consulting user's preferences in a preferences store.
*
* @author Kris De Volder
*/
public class PreferencesBasedSeverityProvider implements SeverityProvider {
private EditorType editorType;
private IPreferenceStore projectPrefs;
private IPreferenceStore workspacePrefs;
private Map<ProblemType, ProblemSeverity> cache = null;
public PreferencesBasedSeverityProvider(IPreferenceStore projectPrefs, IPreferenceStore workspacePrefs, EditorType editorType) {
this.projectPrefs = projectPrefs;
this.workspacePrefs = workspacePrefs;
this.editorType = editorType;
}
public PreferencesBasedSeverityProvider(IProject project, String pluginId, EditorType editorType) {
this(
new ScopedPreferenceStore(new ProjectScope(project), pluginId),
new ScopedPreferenceStore(InstanceScope.INSTANCE, pluginId),
editorType
);
}
@Override public synchronized ProblemSeverity getSeverity(ProblemType problemType) {
if (cache==null) {
cache = new HashMap<>();
}
ProblemSeverity existing = cache.get(problemType);
if (existing==null) {
cache.put(problemType, existing = ProblemSeverityPreferencesUtil.getSeverity(getPrefs(), problemType));
}
return existing;
}
protected IPreferenceStore getPrefs() {
if (useProjectPreferences()) {
return projectPrefs;
} else {
return workspacePrefs;
}
}
private boolean useProjectPreferences() {
if (projectPrefs!=null) {
return ProblemSeverityPreferencesUtil.projectPreferencesEnabled(projectPrefs, editorType);
}
return false;
}
@Override
public synchronized void startSession() {
cache = null;
}
}

View File

@@ -0,0 +1,72 @@
/*******************************************************************************
* Copyright (c) 2015 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.preferences;
import java.io.IOException;
import org.eclipse.jface.preference.IPersistentPreferenceStore;
import org.eclipse.jface.preference.IPreferenceStore;
import org.springframework.ide.eclipse.boot.util.Log;
import org.springframework.ide.eclipse.editor.support.reconcile.ProblemSeverity;
import org.springframework.ide.eclipse.editor.support.reconcile.ProblemType;
import org.springsource.ide.eclipse.commons.core.util.StringUtil;
/**
* @author Kris De Volder
*/
public class ProblemSeverityPreferencesUtil {
public static final String PREFERENCE_PREFIX = "spring.properties.editor.problem.";
public static final String ENABLE_PROJECT_PREFERENCES(EditorType et) {
return PREFERENCE_PREFIX+et+".project.prefs.enabled";
}
public static String getPreferenceName(ProblemType problemType) {
return PREFERENCE_PREFIX+problemType.toString();
}
public static ProblemSeverity getSeverity(IPreferenceStore prefs, ProblemType problemType) {
String value = prefs.getString(getPreferenceName(problemType));
try {
if (StringUtil.hasText(value)) {
return ProblemSeverity.valueOf(value);
}
} catch (Exception e) {
//corrupted data in prefs store? Or maybe its just stale data from a different version
// of STS. Ignore silently and use default value
}
return problemType.getDefaultSeverity();
}
public static void setSeverity(IPreferenceStore prefs, ProblemType problemType, ProblemSeverity severity) {
prefs.setValue(getPreferenceName(problemType), severity.toString());
}
public static boolean projectPreferencesEnabled(IPreferenceStore projectPrefs, EditorType et) {
return projectPrefs.getBoolean(ENABLE_PROJECT_PREFERENCES(et));
}
public static void enableProjectPrefs(IPreferenceStore projectPrefs, EditorType et, boolean enable) {
projectPrefs.setValue(ENABLE_PROJECT_PREFERENCES(et), enable);
}
public static void save(IPreferenceStore preferences) {
if (preferences.needsSaving() && preferences instanceof IPersistentPreferenceStore) {
try {
((IPersistentPreferenceStore) preferences).save();
} catch (IOException e) {
Log.log(e);
}
}
}
}

View File

@@ -0,0 +1,109 @@
/*******************************************************************************
* Copyright (c) 2017 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.reconcile;
import java.util.Iterator;
import org.eclipse.jface.text.IInformationControlCreator;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextHover;
import org.eclipse.jface.text.ITextHoverExtension;
import org.eclipse.jface.text.ITextHoverExtension2;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.IAnnotationModel;
import org.eclipse.jface.text.source.ISourceViewer;
import org.springframework.ide.eclipse.editor.support.hover.HoverInformationControlCreator;
/**
* Base class for annotation hovers
*
* @author Alex Boyko
*
* @param <T> either {@link Annotation} or its subclass
*/
public abstract class AbstractAnnotationHover<T extends Annotation> implements ITextHover, ITextHoverExtension, ITextHoverExtension2 {
final protected ISourceViewer sourceViewer;
final protected QuickfixContext context;
final private Class<T> annotationClass;
public AbstractAnnotationHover(ISourceViewer sourceViewer, QuickfixContext context, Class<T> annotationClass) {
this.sourceViewer = sourceViewer;
this.context = context;
this.annotationClass = annotationClass;
}
@Override
public IInformationControlCreator getHoverControlCreator() {
return new HoverInformationControlCreator(false, "F2 for focus");
}
@Override
public String getHoverInfo(ITextViewer textViewer, IRegion hoverRegion) {
IAnnotationModel model = sourceViewer.getAnnotationModel();
T annot = getAnnotationAt(model, hoverRegion.getOffset());
if (annot != null) {
return annot.getText();
}
return null;
}
@Override
public IRegion getHoverRegion(ITextViewer textViewer, int offset) {
IAnnotationModel model = sourceViewer.getAnnotationModel();
T annot = getAnnotationAt(model, offset);
if (annot != null) {
Position pos = model.getPosition(annot);
if (pos!=null) {
return new Region(pos.getOffset(), pos.getLength());
}
}
return null;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
final protected T getAnnotationAt(IAnnotationModel model, int offset) {
if (model!=null) {
Iterator iter= model.getAnnotationIterator();
T found = null;
Position foundPos = null;
while (iter.hasNext()) {
Object _annotation= iter.next();
if (annotationClass.isAssignableFrom(_annotation.getClass())) {
T annotation = (T) _annotation;
if (acceptAnnotation(annotation)) {
Position pos= model.getPosition(annotation);
if (isAtPosition(offset, pos)) {
if (foundPos==null || pos.length<foundPos.length) {
found = annotation;
foundPos = pos;
}
}
}
}
}
return found;
}
return null;
}
protected boolean acceptAnnotation(T annotation) {
return true;
}
private boolean isAtPosition(int offset, Position pos) {
return (pos != null) && (offset >= pos.getOffset() && offset <= (pos.getOffset() + pos.getLength()));
}
}

View File

@@ -0,0 +1,94 @@
/*******************************************************************************
* Copyright (c) 2015, 2016 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.reconcile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ProjectScope;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.ui.preferences.ScopedPreferenceStore;
import org.springframework.ide.eclipse.editor.support.EditorSupportActivator;
import org.springframework.ide.eclipse.editor.support.util.DocumentUtil;
import org.springframework.ide.eclipse.editor.support.util.UserInteractions;
/**
* Default implementation of {@link QuickfixContext} that derives context information for
* quickfixes from ISourceViewer.
*
* @author Kris De Volder
*/
public class DefaultQuickfixContext implements QuickfixContext {
private final String PLUGIN_ID;
private IPreferenceStore workspacePreferences;
private ISourceViewer sourceViever;
private UserInteractions ui;
public DefaultQuickfixContext(String hostPluginId, IPreferenceStore workspacePreferences, ISourceViewer sourceViewer, UserInteractions ui) {
this.PLUGIN_ID = hostPluginId;
this.workspacePreferences = workspacePreferences;
this.sourceViever = sourceViewer;
this.ui = ui;
}
public IPreferenceStore getPreferences() {
return workspacePreferences;
}
public IProject getProject() {
IDocument doc = getDocument();
if (doc!=null) {
return DocumentUtil.getProject(doc);
}
return null;
}
@Override
public IJavaProject getJavaProject() {
try {
IProject p = getProject();
if (p!=null && p.isAccessible() && p.hasNature(JavaCore.NATURE_ID)) {
return JavaCore.create(p);
}
} catch (Exception e) {
EditorSupportActivator.log(e);
}
return null;
}
@Override
public UserInteractions getUI() {
return ui;
}
@Override
public IPreferenceStore getWorkspacePreferences() {
return workspacePreferences;
}
@Override
public IPreferenceStore getProjectPreferences() {
IProject project = getProject();
if (project!=null) {
return new ScopedPreferenceStore(new ProjectScope(project), PLUGIN_ID);
}
return null;
}
@Override
public IDocument getDocument() {
return sourceViever.getDocument();
}
}

View File

@@ -0,0 +1,30 @@
/*******************************************************************************
* Copyright (c) 2015 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.reconcile;
/**
* Implementation {@link SeverityProvider} that simplies returns the hard-coded 'default severity' based
* from the problem's ProblemType.
*
* @author Kris De Volder
*/
public class DefaultSeverityProvider implements SeverityProvider {
@Override
public ProblemSeverity getSeverity(ProblemType problemType) {
return problemType.getDefaultSeverity();
}
@Override
public void startSession() {
}
}

View File

@@ -0,0 +1,45 @@
/*******************************************************************************
* Copyright (c) 2017 Spring IDE Developers
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Spring IDE Developers - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.reconcile;
import java.util.HashSet;
import java.util.Set;
public class DuplicateFilterProblemCollector implements IProblemCollector {
private Set<ReconcileProblem> seen = new HashSet<>();
private final IProblemCollector delegate;
public DuplicateFilterProblemCollector(IProblemCollector delegate) {
this.delegate = delegate;
}
@Override
public void beginCollecting() {
seen.clear();
delegate.beginCollecting();
}
@Override
public void endCollecting() {
delegate.endCollecting();
seen.clear();
}
@Override
public void accept(ReconcileProblem problem) {
if (seen.add(problem)) {
delegate.accept(problem);
}
}
}

View File

@@ -0,0 +1,19 @@
/*******************************************************************************
* Copyright (c) 2016 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.reconcile;
import java.util.List;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
public interface FixableProblem {
List<ICompletionProposal> getQuickfixes(QuickfixContext context);
}

View File

@@ -0,0 +1,30 @@
/*******************************************************************************
* Copyright (c) 2014-2016 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.reconcile;
public interface IProblemCollector {
void beginCollecting();
void endCollecting();
void accept(ReconcileProblem problem);
/**
* Problem collector that simply ignores/discards anything passed to it.
*/
IProblemCollector NULL = new IProblemCollector() {
public void beginCollecting() {
}
public void endCollecting() {
}
public void accept(ReconcileProblem problem) {
}
};
}

View File

@@ -0,0 +1,18 @@
/*******************************************************************************
* Copyright (c) 2015 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.reconcile;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.text.IDocument;
public interface IReconcileEngine {
public void reconcile(IDocument doc, IProblemCollector problemCollector, IProgressMonitor mon);
}

View File

@@ -0,0 +1,22 @@
/*******************************************************************************
* Copyright (c) 2014, 2016 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.reconcile;
/**
* @author Kris De Volder
*/
public enum ProblemSeverity {
IGNORE,
WARNING,
ERROR;
}

View File

@@ -0,0 +1,29 @@
/*******************************************************************************
* Copyright (c) 2015 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.reconcile;
/**
* Besides the methods below, the only hard requirement for a 'problem type' is
* that it is a unique object that is not 'equals' to any other object.
* <p>
* It is probably nice if you implement a good toString however.
* <p>
* A good way to implement a discrete set of problemType objects is as an enum
* that implements this interace.
*
* @author Kris De Volder
*/
public interface ProblemType {
String getId();
ProblemSeverity getDefaultSeverity();
String getLabel();
String getDescription();
}

View File

@@ -0,0 +1,16 @@
/*******************************************************************************
* Copyright (c) 2017 Spring IDE Developers
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Spring IDE Developers - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.reconcile;
public interface ProblemTypeProvider {
ProblemType getProblemType();
}

View File

@@ -0,0 +1,32 @@
/*******************************************************************************
* Copyright (c) 2015, 2016 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.reconcile;
import org.eclipse.core.resources.IProject;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.IDocument;
import org.springframework.ide.eclipse.editor.support.util.UserInteractions;
/**
* Provides access to additional context info and objects that quickfixes might
* need in order to be able to apply themselves.
*
* @author Kris De Volder
*/
public interface QuickfixContext {
IProject getProject();
IPreferenceStore getWorkspacePreferences();
IPreferenceStore getProjectPreferences();
IJavaProject getJavaProject();
UserInteractions getUI();
IDocument getDocument();
}

View File

@@ -0,0 +1,57 @@
/*******************************************************************************
* Copyright (c) 2017 Spring IDE Developers
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Spring IDE Developers - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.reconcile;
import org.eclipse.core.runtime.Assert;
import org.springframework.ide.eclipse.editor.support.util.ValueParseException;
import org.springsource.ide.eclipse.commons.core.util.StringUtil;
/**
* Exception if there is a failure when parsing a value. It does not wrap
* other exceptions such that when thrown, the parse exception is the "deepest"
* error.
*
*/
public class ReconcileException extends ValueParseException implements ProblemTypeProvider {
private static final long serialVersionUID = 1L;
private final ProblemType problemType;
private ReplacementQuickfix replacement = null; //Optional info to create a 'replacement quickfix' for the value
public ReconcileException(String message, ProblemType problemType) {
super(message);
this.problemType = problemType;
}
public ReconcileException(String message, ProblemType problemType, int start, int end) {
super(message, start, end);
this.problemType = problemType;
}
@Override
public ProblemType getProblemType() {
return problemType;
}
public ReconcileException fixWith(ReplacementQuickfix replacement) {
//Silently ignore if the fix is either null or doesn't provide a proper replacement text.
if (replacement!=null && StringUtil.hasText(replacement.replacement)) {
Assert.isLegal(this.replacement==null, "Multiple fixes not yet supported");
this.replacement = replacement;
}
return this;
}
public ReplacementQuickfix getReplacement() {
return replacement;
}
}

View File

@@ -0,0 +1,24 @@
/*******************************************************************************
* Copyright (c) 2016 Pivotal Software, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal Software, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.reconcile;
/**
* Minamal interface that objects representing a reconciler problem must
* implement.
*
* @author Kris De Volder
*/
public interface ReconcileProblem {
ProblemType getType();
String getMessage();
int getOffset();
int getLength();
}

View File

@@ -0,0 +1,88 @@
/*******************************************************************************
* Copyright (c) 2006, 2008, 20014 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
* Kris De Volder - copied from SpellingAnnotation to become
* 'SpringPropertyAnnotation'.
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.reconcile;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.jface.text.quickassist.IQuickFixableAnnotation;
import org.eclipse.jface.text.source.Annotation;
/**
* Spelling annotation.
*
* @since 3.3
*/
@SuppressWarnings("restriction")
public class ReconcileProblemAnnotation extends Annotation implements IQuickFixableAnnotation {
/** Annotation type for error and warnings*/
public static final String ERROR_ANNOTATION_TYPE = org.eclipse.jdt.internal.ui.javaeditor.JavaMarkerAnnotation.ERROR_ANNOTATION_TYPE;
public static final String WARNING_ANNOTATION_TYPE = org.eclipse.jdt.internal.ui.javaeditor.JavaMarkerAnnotation.WARNING_ANNOTATION_TYPE;
//Could use our own annotation type (but then we also have to declare it somehow to make it show error style marker)
public static final Set<String> TYPES = new HashSet<String>();
static {
TYPES.add(ERROR_ANNOTATION_TYPE);
TYPES.add(WARNING_ANNOTATION_TYPE);
}
public static String getAnnotationType(ProblemSeverity severity) {
switch (severity) {
case ERROR:
return ERROR_ANNOTATION_TYPE;
case WARNING:
return WARNING_ANNOTATION_TYPE;
case IGNORE:
return null;
default:
throw new IllegalStateException("Bug: Missing switch case!");
}
}
private ReconcileProblem fProblem;
/**
* Creates a new annotation of given type.
*/
public ReconcileProblemAnnotation(String annotationType, ReconcileProblem problem) {
super(annotationType, false, problem.getMessage());
fProblem = problem;
}
/*
* @see org.eclipse.jface.text.quickassist.IQuickFixableAnnotation#isQuickFixable()
*/
public boolean isQuickFixable() {
return true;
}
/*
* @see org.eclipse.jface.text.quickassist.IQuickFixableAnnotation#isQuickFixableStateSet()
*/
public boolean isQuickFixableStateSet() {
return true;
}
/*
* @see org.eclipse.jface.text.quickassist.IQuickFixableAnnotation#setQuickFixable(boolean)
*/
public void setQuickFixable(boolean state) {
// always true
}
public ReconcileProblem getSpringPropertyProblem() {
return fProblem;
}
}

View File

@@ -0,0 +1,34 @@
/*******************************************************************************
* Copyright (c) 2015, 2017 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.reconcile;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.source.IAnnotationModel;
import org.eclipse.jface.text.source.ISourceViewer;
public class ReconcileProblemAnnotationHover extends AbstractAnnotationHover<ReconcileProblemAnnotation> {
public ReconcileProblemAnnotationHover(ISourceViewer sourceViewer, QuickfixContext context) {
super(sourceViewer, context, ReconcileProblemAnnotation.class);
}
@Override
public Object getHoverInfo2(ITextViewer textViewer, IRegion hoverRegion) {
IAnnotationModel model = sourceViewer.getAnnotationModel();
ReconcileProblemAnnotation annot = getAnnotationAt(model, hoverRegion.getOffset());
if (annot!=null) {
return new SpringPropertyProblemHoverInfo(annot.getSpringPropertyProblem(), context);
}
return null;
}
}

View File

@@ -0,0 +1,90 @@
/*******************************************************************************
* Copyright (c) 2015, 2016 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.reconcile;
/**
* An implementation of {@link ReconcileProblem} that is just a simple data object.
*
* @author Kris De Volder
*/
public class ReconcileProblemImpl implements ReconcileProblem {
final private ProblemType type;
final private String msg;
final private int offset;
final private int len;
public ReconcileProblemImpl(ProblemType type, String msg, int offset, int len) {
super();
this.type = type;
this.msg = msg;
this.offset = offset;
this.len = len;
}
@Override
public ProblemType getType() {
return type;
}
@Override
public String getMessage() {
return msg;
}
@Override
public int getOffset() {
return offset;
}
@Override
public int getLength() {
return len;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + len;
result = prime * result + ((msg == null) ? 0 : msg.hashCode());
result = prime * result + offset;
result = prime * result + ((type == null) ? 0 : type.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ReconcileProblemImpl other = (ReconcileProblemImpl) obj;
if (len != other.len)
return false;
if (msg == null) {
if (other.msg != null)
return false;
} else if (!msg.equals(other.msg))
return false;
if (offset != other.offset)
return false;
if (type == null) {
if (other.type != null)
return false;
} else if (!type.equals(other.type))
return false;
return true;
}
}

View File

@@ -0,0 +1,198 @@
/*******************************************************************************
* Copyright (c) 2015, 2016 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.reconcile;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ISynchronizable;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.reconciler.DirtyRegion;
import org.eclipse.jface.text.reconciler.IReconcilingStrategy;
import org.eclipse.jface.text.reconciler.IReconcilingStrategyExtension;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.IAnnotationModel;
import org.eclipse.jface.text.source.IAnnotationModelExtension;
import org.eclipse.jface.text.source.ISourceViewer;
/**
* Adapts our {@link IReconcileEngine} interface to an Eclipse {@link ReconcileStrategy}.
*
* @author Kris De Volder
*/
public class ReconcileStrategy implements IReconcilingStrategy, IReconcilingStrategyExtension {
public static final SeverityProvider DEFAULT_SEVERITY_PROVIDER = new DefaultSeverityProvider();
private ISourceViewer fViewer;
private IReconcileEngine fEngine;
private IDocument fDocument;
private IProgressMonitor fProgressMonitor;
private IProblemCollector fProblemCollector;
public ReconcileStrategy(ISourceViewer viewer, IReconcileEngine engine) {
this.fViewer = viewer;
this.fEngine = engine;
}
/*
* @see org.eclipse.jface.text.reconciler.IReconcilingStrategyExtension#initialReconcile()
*/
public void initialReconcile() {
reconcile(new Region(0, fDocument.getLength()));
}
/*
* @see org.eclipse.jface.text.reconciler.IReconcilingStrategy#reconcile(org.eclipse.jface.text.reconciler.DirtyRegion,org.eclipse.jface.text.IRegion)
*/
public void reconcile(DirtyRegion dirtyRegion, IRegion subRegion) {
try {
IRegion startLineInfo= fDocument.getLineInformationOfOffset(subRegion.getOffset());
IRegion endLineInfo= fDocument.getLineInformationOfOffset(subRegion.getOffset() + Math.max(0, subRegion.getLength() - 1));
if (startLineInfo.getOffset() == endLineInfo.getOffset())
subRegion= startLineInfo;
else
subRegion= new Region(startLineInfo.getOffset(), endLineInfo.getOffset() + Math.max(0, endLineInfo.getLength() - 1) - startLineInfo.getOffset());
} catch (BadLocationException e) {
subRegion= new Region(0, fDocument.getLength());
}
reconcile(subRegion);
}
/*
* @see org.eclipse.jface.text.reconciler.IReconcilingStrategy#reconcile(org.eclipse.jface.text.IRegion)
*/
public void reconcile(IRegion region) {
if (getAnnotationModel() == null || fProblemCollector == null)
return;
//Note: This isn't an 'incremental' reconciler. It always checks the whole document. The dirty
// region is ignored.
fEngine.reconcile(fDocument, fProblemCollector, fProgressMonitor);
}
/**
* Returns the annotation model to be used by this reconcile strategy.
*
* @return the annotation model of the underlying editor input or
* <code>null</code> if none could be determined
*/
protected IAnnotationModel getAnnotationModel() {
return fViewer.getAnnotationModel();
}
/*
* @see org.eclipse.jface.text.reconciler.IReconcilingStrategyExtension#setProgressMonitor(org.eclipse.core.runtime.IProgressMonitor)
*/
public final void setProgressMonitor(IProgressMonitor monitor) {
fProgressMonitor= monitor;
}
@Override
public void setDocument(IDocument document) {
fDocument= document;
fProblemCollector= createProblemCollector();
}
protected IDocument getDocument() {
return fDocument;
}
protected IProblemCollector createProblemCollector() {
IAnnotationModel model= getAnnotationModel();
if (model == null)
return null;
return new SeverityAwareProblemCollector(model);
}
/**
* Problem collector.
*/
private class SeverityAwareProblemCollector implements IProblemCollector {
/** Annotation model. */
private IAnnotationModel fAnnotationModel;
/** Annotations to add. */
private Map<Annotation, Position> fAddAnnotations;
/** Lock object for modifying the annotations. */
private Object fLockObject;
/**
* Initializes this collector with the given annotation model.
*
* @param annotationModel the annotation model
*/
public SeverityAwareProblemCollector(IAnnotationModel annotationModel) {
Assert.isLegal(annotationModel != null);
fAnnotationModel= annotationModel;
if (fAnnotationModel instanceof ISynchronizable)
fLockObject= ((ISynchronizable)fAnnotationModel).getLockObject();
else
fLockObject= fAnnotationModel;
}
public void accept(ReconcileProblem problem) {
ProblemSeverity severity = getSeverities().getSeverity(problem);
String annotationType = ReconcileProblemAnnotation.getAnnotationType(severity);
if (annotationType!=null) {
fAddAnnotations.put(new ReconcileProblemAnnotation(annotationType, problem), new Position(problem.getOffset(), problem.getLength()));
}
}
public void beginCollecting() {
getSeverities().startSession();
fAddAnnotations= new HashMap<>();
}
public void endCollecting() {
List<Annotation> toRemove= new ArrayList<>();
synchronized (fLockObject) {
Iterator<Annotation> iter= fAnnotationModel.getAnnotationIterator();
while (iter.hasNext()) {
Annotation annotation= iter.next();
if (ReconcileProblemAnnotation.TYPES.contains(annotation.getType()))
toRemove.add(annotation);
}
Annotation[] annotationsToRemove= toRemove.toArray(new Annotation[toRemove.size()]);
if (fAnnotationModel instanceof IAnnotationModelExtension)
((IAnnotationModelExtension)fAnnotationModel).replaceAnnotations(annotationsToRemove, fAddAnnotations);
else {
for (int i= 0; i < annotationsToRemove.length; i++)
fAnnotationModel.removeAnnotation(annotationsToRemove[i]);
for (iter= fAddAnnotations.keySet().iterator(); iter.hasNext();) {
Annotation annotation= iter.next();
fAnnotationModel.addAnnotation(annotation, fAddAnnotations.get(annotation));
}
}
}
fAddAnnotations= null;
}
}
protected SeverityProvider getSeverities() {
return DEFAULT_SEVERITY_PROVIDER;
}
}

View File

@@ -0,0 +1,32 @@
/*******************************************************************************
* Copyright (c) 2017 Spring IDE Developers
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Spring IDE Developers - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.reconcile;
/**
* An info object that can be attached to a {@link ReconcileException} to indicate that
* the problem can be fixed by a simple value substitution at the exact location of
* the problem marker.
*
* @author Kris De Volder
*/
public class ReplacementQuickfix {
public final String msg;
public final String replacement;
public ReplacementQuickfix(String msg, String replacement) {
super();
this.msg = msg;
this.replacement = replacement;
}
@Override
public String toString() {
return "ReplacementQuickfix [msg=" + msg + ", replacement=" + replacement + "]";
}
}

View File

@@ -0,0 +1,32 @@
/*******************************************************************************
* Copyright (c) 2015 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.reconcile;
/**
* Provides a means to map {@link ReconcileProblem} to a {@link ProblemSeverity}. Problems mapped to 'IGNORE'
* severity will suppressed.
*
* @author Kris De Volder
*/
public interface SeverityProvider {
ProblemSeverity getSeverity(ProblemType problemType);
/**
* Called at the start of a reconciling/validation session. This can be used by SeverityProviders that cache severities
* as a good time clear the cache.
*/
void startSession();
default ProblemSeverity getSeverity(ReconcileProblem problem) {
return getSeverity(problem.getType());
}
}

View File

@@ -0,0 +1,90 @@
/*******************************************************************************
* Copyright (c) 2015 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.reconcile;
import java.util.List;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.springframework.ide.eclipse.editor.support.hover.HoverInfo;
import org.springframework.ide.eclipse.editor.support.util.HtmlBuffer;
import com.google.common.collect.ImmutableList;
/**
* @author Kris De Volder
*/
public class SpringPropertyProblemHoverInfo extends HoverInfo {
private ReconcileProblem problem;
private QuickfixContext context;
public SpringPropertyProblemHoverInfo(ReconcileProblem problem, QuickfixContext context) {
this.problem = problem;
this.context = context;
}
@Override
protected String renderAsHtml() {
HtmlBuffer html = new HtmlBuffer();
html.text(problem.getMessage());
renderQuickfixes(html, getQuickfixes(problem));
return html.toString();
}
private List<ICompletionProposal> getQuickfixes(ReconcileProblem problem) {
if (problem instanceof FixableProblem) {
return ((FixableProblem)problem).getQuickfixes(context);
}
return ImmutableList.of();
}
private void renderQuickfixes(HtmlBuffer html, List<ICompletionProposal> quickfixes) {
if (!quickfixes.isEmpty()) {
html.hline();
if (quickfixes.size()==1) {
html.p("1 quickfix available:");
} else {
html.text(quickfixes.size()+" quickfixes available:");
}
html.raw("<ul>");
for (ICompletionProposal fix : quickfixes) {
html.raw("<li>");
actionLink(html, fix.getDisplayString(), applyQuickfix(fix));
}
html.raw("</ul>");
//Yuck... we need to add some extra empty pars at end or the browser information control is
// sized too small and doesn't show the quickfixes.
html.p("");
html.p("");
html.p("");
html.p("");
}
}
private Runnable applyQuickfix(final ICompletionProposal fix) {
return new Runnable() {
public void run() {
//TODO: need document in general, but currently implemented quickfixes don't care so
// we can get away with this for now.
fix.apply(null);
}
};
}
@Override
public String toString() {
return "SpringPropertyProblemHoverInfo("+problem.getMessage()+")";
}
}

View File

@@ -0,0 +1,24 @@
/*******************************************************************************
* Copyright (c) 2015 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.util;
import java.util.Collection;
/**
* @author Kris De Volder
*/
public class CollectionUtil {
public static <E> boolean hasElements(Collection<E> c) {
return c!=null && !c.isEmpty();
}
}

View File

@@ -0,0 +1,39 @@
/*******************************************************************************
* Copyright (c) 2015 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.util;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* {@link IRequestor} that simplies stores all items received into
* a List
*
* @author Kris De Volder
*/
public class Collector<T> implements IRequestor<T> {
@SuppressWarnings("unchecked")
private List<T> nodes = Collections.EMPTY_LIST;
@Override
public void accept(T node) {
if (nodes==Collections.EMPTY_LIST) {
nodes = new ArrayList<T>();
}
nodes.add(node);
}
public List<T> get() {
return nodes;
}
}

View File

@@ -0,0 +1,43 @@
/*******************************************************************************
* Copyright (c) 2015 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. and others - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.util;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.widgets.Display;
public class ColorManager {
public static final RGB GREY = new RGB(128,128,128);
public static final RGB CYAN = new RGB(0,128,128);
private static ColorManager instance;
protected Map<RGB,Color> fColorTable = new HashMap<RGB,Color>();
public Color getColor(RGB rgb) {
Color color = fColorTable.get(rgb);
if (color == null) {
color = new Color(Display.getCurrent(), rgb);
fColorTable.put(rgb, color);
}
return color;
}
public static ColorManager getInstance() {
if (instance==null) {
instance = new ColorManager();
}
return instance;
}
}

View File

@@ -0,0 +1,66 @@
/*******************************************************************************
* Copyright (c) 2015 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.util;
import org.eclipse.core.resources.IContainer;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.dialogs.ElementListSelectionDialog;
/**
* Provides 'real' implementations of the UserInteractions (in testing context
* a 'mock' UserInteractions object can be used instead.
*
* @author Kris De Volder
*/
public class DefaultUserInteractions implements UserInteractions {
private Shell shell;
public DefaultUserInteractions(Shell shell) {
this.shell = shell;
}
@Override
public IContainer chooseOneSourceFolder(String title, String message, IContainer[] options, IContainer preferred) {
ElementListSelectionDialog dialog = new ElementListSelectionDialog(shell, new LabelProvider() {
@Override
public String getText(Object element) {
if (element instanceof IContainer) {
IContainer c = (IContainer) element;
return c.getFullPath().toString();
}
return element.toString();
}
});
dialog.setTitle(title);
dialog.setMessage(message);
dialog.setElements(options);
dialog.setBlockOnOpen(true);
dialog.setMultipleSelection(false);
if (preferred!=null) {
dialog.setInitialSelections(new Object[] {preferred});
}
int code = dialog.open();
if (code == Window.OK) {
return (IContainer) dialog.getFirstResult();
}
return null;
}
@Override
public void error(String title, String message) {
MessageDialog.openError(shell, title, message);
}
}

View File

@@ -0,0 +1,305 @@
/*******************************************************************************
* Copyright (c) 2016-2017 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.util;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITypedRegion;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.TypedRegion;
/**
* A non-sucky alternative to {@link IRegion}. Represents a region of text in a document.
* <p>
* Caution: assumes the underlying document is not mutated during the lifetime of the
* region object (otherwise start/end positions may no longer be valid).
* <p>
* Implements {@link CharSequence} for convenience (e.g you can use {@link DocumentRegion} as
* input to a {@link Pattern} and other standard JRE functions which expect a {@link CharSequence}.
*
* @author Kris De Volder
*/
public class DocumentRegion implements CharSequence {
final IDocument doc;
final int start;
final int end;
public DocumentRegion(IDocument doc, IRegion r) {
this(doc,
r.getOffset(),
r.getOffset()+r.getLength()
);
}
/**
* Constructs a {@link DocumentRegion} on a given document. Tries its
* best to behave sensibly when passed 'strange' coordinates by
* adjusting them logically rather than throw an Exception.
* <p>
* A position before the start of the document is moved to be the start
* of the document.
* <p>
* A position after the end of the document is moved to the end
* of the document.
* <p>
* If 'end' position is before the start position it is moved be
* exactly at the start position (this avoids region with
* negative length).
*/
public DocumentRegion(IDocument doc, int start, int end) {
this.doc = doc;
this.start = limitRange(start, 0, doc.getLength());
this.end = limitRange(end, start, doc.getLength());
}
/**
* Create {@link DocumentRegion} covering the whole document.
*/
public DocumentRegion(IDocument doc) {
this(doc, 0, doc.getLength());
}
private int limitRange(int offset, int min, int max) {
if (offset<min) {
return min;
}
if (offset>max) {
return max;
}
return offset;
}
@Override
public String toString() {
return DocumentUtil.textBetween(doc, start, end);
}
public DocumentRegion trim() {
return trimEnd().trimStart();
}
public DocumentRegion trimStart() {
int howMany = 0;
int len = length();
while (howMany<len && Character.isWhitespace(charAt(howMany))) {
howMany++;
}
return subSequence(howMany, len);
}
public DocumentRegion trimEnd() {
int howMany = 0; //how many chars to remove from the end
int len = length();
int lastChar = len-1;
while (howMany<len && Character.isWhitespace(charAt(lastChar-howMany))) {
howMany++;
}
if (howMany>0) {
return subSequence(0, len-howMany);
}
return this;
}
/**
* Gets character from the region, offset from the start of the region
* @return the character from the document (char)0 if the offset is outside the region.
*/
@Override
public char charAt(int offset) {
if (offset<0 || offset>=length()) {
throw new IndexOutOfBoundsException(""+offset);
}
try {
return doc.getChar(start+offset);
} catch (BadLocationException e) {
throw new IndexOutOfBoundsException(""+offset);
}
}
@Override
public int length() {
return end-start;
}
@Override
public DocumentRegion subSequence(int start, int end) {
int len = length();
Assert.isLegal(start>=0);
Assert.isLegal(end<=len);
if (start==0 && end==len) {
return this;
}
return new DocumentRegion(doc, this.start+start, this.start+end);
}
public boolean isEmpty() {
return length()==0;
}
public DocumentRegion subSequence(int start) {
return subSequence(start, length());
}
public IRegion asRegion() {
return new Region(start, end-start);
}
public ITypedRegion asTypedRegion(String type) {
return new TypedRegion(start, length(), type);
}
public int indexOf(char ch, int fromIndex) {
while (fromIndex < length()) {
if (charAt(fromIndex)==ch) {
return fromIndex;
}
fromIndex++;
}
return -1;
}
public DocumentRegion[] split(char c) {
List<DocumentRegion> pieces = new ArrayList<>();
int start = 0;
int end;
while ((end=indexOf(c, start))>=0) {
pieces.add(subSequence(start, end));
start = end+1;
}
// Do not forget the last piece!
pieces.add(subSequence(start, length()));
return pieces.toArray(new DocumentRegion[pieces.size()]);
}
public DocumentRegion[] split(Pattern delimiter) {
List<DocumentRegion> pieces = new ArrayList<>();
int start = 0;
Matcher matcher = delimiter.matcher(this);
while (matcher.find(start)) {
int end = matcher.start();
pieces.add(subSequence(start, end));
start = matcher.end();
}
// Do not forget the last piece!
pieces.add(subSequence(start, length()));
return pieces.toArray(new DocumentRegion[pieces.size()]);
}
/**
* Removes a single occurrence of pat from the start of this region.
*/
public DocumentRegion trimStart(Pattern pat) {
pat = Pattern.compile("^("+pat.pattern()+")");
Matcher matcher = pat.matcher(this);
if (matcher.find()) {
return subSequence(matcher.end());
}
return this;
}
/**
* Removes a single occurrence of pat from the end of this region.
*/
public DocumentRegion trimEnd(Pattern pat) {
pat = Pattern.compile("("+pat.pattern()+")$");
Matcher matcher = pat.matcher(this);
if (matcher.find()) {
return subSequence(0, matcher.start());
}
return this;
}
/**
* Get the region after this one with a given lenght.
* <p>
* If the document is too short to provide the requested lenght
* then the region is truncated to end of the document.
*/
public DocumentRegion textAfter(int len) {
Assert.isLegal(len>=0);
return new DocumentRegion(doc, end, end+len);
}
/**
* Get the region before this one with a given lenght.
* <p>
* If the requested region extends before the start of the document,
* then the region is shortened so its start coincides with document start.
*/
public DocumentRegion textBefore(int len) {
Assert.isLegal(len>=0);
return new DocumentRegion(doc, start-len, start);
}
public IDocument getDocument() {
return doc;
}
/**
* Get the start of this region in 'absolute' terms (i.e. relative to the document).
*/
public int getStart() {
return start;
}
/**
* Get the end of this region in 'absolute' terms (i.e. relative to the document).
*/
public int getEnd() {
return end;
}
/**
* Convert the given document offset into an offset relative to this region.
*/
public int toRelative(int offset) {
return offset-start;
}
public int getLength() {
return length();
}
public boolean endsWith(CharSequence string) {
int myLen = length();
int strLen = string.length();
if (myLen>=strLen) {
for (int i = 0; i < strLen; i++) {
if (charAt(myLen-strLen+i)!=string.charAt(i)) {
return false;
}
}
return true;
}
return false;
}
/**
* Take documentRegion at the end of
*/
public DocumentRegion textAtEnd(int numChars) {
numChars = Math.min(getLength(), numChars);
return new DocumentRegion(doc, end-numChars, end);
}
// public Range asRange() throws BadLocationException {
// return doc.toRange(asRegion());
// }
}

View File

@@ -0,0 +1,161 @@
/*******************************************************************************
* Copyright (c) 2014-2016 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.util;
import org.eclipse.core.filebuffers.FileBuffers;
import org.eclipse.core.filebuffers.ITextFileBuffer;
import org.eclipse.core.filebuffers.ITextFileBufferManager;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IPath;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.springframework.ide.eclipse.editor.support.EditorSupportActivator;
public class DocumentUtil {
/**
* Returns location of the resource underlying the document. May return null
* if the location can not be determined.
*/
public static IPath getLocation(IDocument doc) {
ITextFileBufferManager bufferMgr = FileBuffers.getTextFileBufferManager();
if (bufferMgr!=null) {
ITextFileBuffer buf = bufferMgr.getTextFileBuffer(doc);
if (buf!=null) {
return buf.getLocation();
}
}
return null;
}
public static IProject getProject(IDocument doc) {
IPath location = getLocation(doc);
if (location!=null) {
if (location.segmentCount()>=1) {
String projectName = location.segment(0);
return ResourcesPlugin.getWorkspace().getRoot().getProject(projectName);
}
}
return null;
}
public static IJavaProject getJavaProject(IDocument doc) {
try {
if (doc!=null) {
IProject p = getProject(doc);
if (p!=null && p.isAccessible() && p.hasNature(JavaCore.NATURE_ID)) {
return JavaCore.create(p);
}
}
} catch (Exception e) {
EditorSupportActivator.log(e);
}
return null;
}
public static int firstNonWhitespaceCharOfRegion(IDocument doc, IRegion region) {
try {
int pos = skipWhiteSpace(doc, region.getOffset());
if (pos<region.getOffset()+region.getLength()) {
return pos;
}
} catch (Exception e) {
EditorSupportActivator.log(e);
}
return -1;
}
/**
* Compute location of the last character in the region that is not a whitespace character.
*/
public static int lastNonWhitespaceCharOfRegion(IDocument doc, IRegion errorRegion) {
try {
int pos = errorRegion.getOffset()+errorRegion.getLength()-1;
while (pos>=errorRegion.getOffset()&&Character.isWhitespace(doc.getChar(pos))) {
pos--;
}
if (pos>=errorRegion.getOffset()) {
return pos;
}
} catch (Exception e) {
EditorSupportActivator.log(e);
}
return -1;
}
public static int skipWhiteSpace(IDocument doc, int pos) {
try {
int end = doc.getLength();
while (pos<end&&Character.isWhitespace(doc.getChar(pos))) {
pos++;
}
if (pos<end) {
return pos;
}
} catch (Exception e) {
EditorSupportActivator.log(e);
}
return -1;
}
/**
* Fetch text between two offsets. Doesn't throw BadLocationException.
* If either one or both of the offsets points outside the
* document then they will be adjusted to point the appropriate boundary to
* retrieve the text just upto the end or beginning of the document instead.
*/
public static String textBetween(IDocument doc, int start, int end) {
Assert.isLegal(start<=end);
if (start>=doc.getLength()) {
return "";
}
if (start<0) {
start = 0;
}
if (end>doc.getLength()) {
end = doc.getLength();
}
if (end<start) {
end = start;
}
try {
return doc.get(start, end-start);
} catch (BadLocationException e) {
//unless the code above is wrong... this is supposed to be impossible!
throw new IllegalStateException("Bug!", e);
}
}
// public static IRegion trim(IDocument doc, IRegion region) {
// if (region.getLength()==0) {
// //Special case avoid doing any work for empty region trimming.
// return region;
// }
// int start = firstNonWhitespaceCharOfRegion(doc, region);
// if (start>=0) {
// int end = lastNonWhitespaceCharOfRegion(doc, region);
// if (end>=start) {
// return new Region(start, end-start+1); //+1 because 'end' character should be included.
// }
// }
// //No non-whitespace chars found. It is somewhat ambiguous how to trim
// // the region down to 'nothing'. We can essentially pick any offset within the
// // region as the start of the empty region. We decided to just pick the start
// // of the region (so trim from the end).
// return new Region(region.getOffset(), 0);
// }
}

View File

@@ -0,0 +1,45 @@
/*******************************************************************************
* Copyright (c) 2014-2016 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.util;
import java.util.Collection;
import java.util.Set;
import com.google.common.collect.ImmutableSet;
/**
* Parser for checking a 'Enum' style values.
*
* @author Kris De Volder
*/
public class EnumValueParser implements ValueParser {
private String typeName;
private Set<String> values;
public EnumValueParser(String typeName, String... values) {
this(typeName, ImmutableSet.copyOf(values));
}
public EnumValueParser(String typeName, Collection<String> values) {
this.typeName = typeName;
this.values = ImmutableSet.copyOf(values);
}
public Object parse(String str) {
if (values.contains(str)) {
return str;
} else {
throw new IllegalArgumentException("'"+str+"' is not valid for Enum '"+typeName+"'");
}
}
}

View File

@@ -0,0 +1,34 @@
/*******************************************************************************
* Copyright (c) 2015 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.util;
/**
* This class was moved to commons.core for more wide reuse. This old copy
* is deprecated.
*
* @author Kris De Volder
*/
@Deprecated
public class FuzzyMatcher {
/**
* Match given pattern with a given data. The data is considered a 'match' for the
* pattern if all characters in the pattern can be found in the data, in the
* same order but with possible 'gaps' in between.
* <p>
* The function returns 0. when the pattern doesn't match the data and a non-zero
* 'score' when it does. The higher the score, the better the match is considered to
* be.
*/
public static double matchScore(String pattern, String data) {
return org.springsource.ide.eclipse.commons.core.util.FuzzyMatcher.matchScore(pattern, data);
}
}

View File

@@ -0,0 +1,167 @@
/*******************************************************************************
* Copyright (c) 2014-2020 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.util;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLEncoder;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jdt.ui.PreferenceConstants;
import org.eclipse.jface.internal.text.html.BrowserInformationControl;
import org.eclipse.jface.internal.text.html.HTMLPrinter;
import org.eclipse.jface.resource.ColorRegistry;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.text.DefaultInformationControl;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.RGB;
import org.osgi.framework.Bundle;
import org.springframework.ide.eclipse.editor.support.EditorSupportActivator;
/**
* Helper class to make it a little easier to create simple html page (for display in
* {@link DefaultInformationControl} or {@link BrowserInformationControl}
*
* @author Kris De Volder
*/
@SuppressWarnings("restriction")
public class HtmlBuffer {
private StringBuilder buffer = new StringBuilder();
private boolean epilogAdded = false; //to ensure only added once.
private static String fgCSSStyles;
public HtmlBuffer() {
this.buffer = new StringBuilder();
}
/**
* Append text, applies escaping to the text as needed.
*/
public void text(String text) {
raw(HTMLPrinter.convertToHTMLContent(text));
}
/**
* Append 'raw' text. Doesn't apply any escaping.
*/
public void raw(String rawText) {
if (epilogAdded) {
throw new IllegalStateException("Can not append more text after epilog was added");
}
buffer.append(rawText);
}
/**
* Append text, applies urlencoding to the text.
*/
public void url(String string) {
try {
raw(URLEncoder.encode(string, "utf8"));
} catch (UnsupportedEncodingException e) {
EditorSupportActivator.log(e);
}
}
public String toString() {
if (!epilogAdded && buffer.length()>0) {
epilogAdded = true;
ColorRegistry registry = JFaceResources.getColorRegistry();
RGB fgRGB = registry.getRGB("org.eclipse.jdt.ui.Javadoc.foregroundColor"); //$NON-NLS-1$
RGB bgRGB= registry.getRGB("org.eclipse.jdt.ui.Javadoc.backgroundColor"); //$NON-NLS-1$
HTMLPrinter.insertPageProlog(buffer, 0, fgRGB, bgRGB, getCSSStyles());
HTMLPrinter.addPageEpilog(buffer);
}
return buffer.toString();
}
/**
* Note: copied from org.eclipse.jdt.internal.ui.text.java.AbstractJavaCompletionProposal.getCSSStyles()
* Returns the style information for displaying HTML (Javadoc) content.
*
* @return the CSS styles
* @since 3.3
*/
public static String getCSSStyles() {
if (fgCSSStyles == null) {
Bundle bundle= Platform.getBundle(JavaPlugin.getPluginId());
URL url= bundle.getEntry("/JavadocHoverStyleSheet.css"); //$NON-NLS-1$
if (url != null) {
BufferedReader reader= null;
try {
url= FileLocator.toFileURL(url);
reader= new BufferedReader(new InputStreamReader(url.openStream()));
StringBuffer buffer= new StringBuffer(200);
String line= reader.readLine();
while (line != null) {
buffer.append(line);
buffer.append('\n');
line= reader.readLine();
}
fgCSSStyles= buffer.toString();
} catch (IOException ex) {
JavaPlugin.log(ex);
} finally {
try {
if (reader != null)
reader.close();
} catch (IOException e) {
}
}
}
}
String css= fgCSSStyles;
if (css != null) {
FontData fontData= JFaceResources.getFontRegistry().getFontData(PreferenceConstants.APPEARANCE_JAVADOC_FONT)[0];
css= HTMLPrinter.convertTopLevelFont(css, fontData);
}
return css;
}
public void hline() {
raw("<hr>");
}
public void p(String string) {
raw("<p>");
text(string);
raw("</p>");
}
public void snippet(HtmlSnippet snippet) {
snippet.render(this);
}
public void bold(String string) {
raw("<b>");
text(string);
raw("</b>");
}
public void href(String url, String displayText) {
raw("<a href=\"" + url + "\">");
text(displayText);
raw("</a>");
}
}

View File

@@ -0,0 +1,65 @@
/*******************************************************************************
* Copyright (c) 2016, 2020 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.util;
/**
* A snippet that can be rendered into html.
*
* @author Kris De Volder
*/
@FunctionalInterface
public interface HtmlSnippet {
void render(HtmlBuffer html);
default public String toHtml() {
HtmlBuffer buf = new HtmlBuffer();
render(buf);
return buf.toString();
}
// Create snippets:
public static HtmlSnippet text(final String text) {
return new HtmlSnippet() {
@Override
public void render(HtmlBuffer html) {
html.text(text);
}
};
}
public static HtmlSnippet raw(final String rawHtml) {
return new HtmlSnippet() {
@Override
public void render(HtmlBuffer html) {
html.raw(rawHtml);
}
};
}
public static HtmlSnippet italic(String text) {
return italic(text(text));
}
public static HtmlSnippet italic(final HtmlSnippet wrappee) {
return new HtmlSnippet() {
@Override
public void render(HtmlBuffer html) {
html.raw("<i>");
wrappee.render(html);
html.raw("</i>");
}
};
}
// add more as needed ...
}

View File

@@ -0,0 +1,21 @@
/*******************************************************************************
* Copyright (c) 2015, 2016 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.util;
public class HtmlUtil {
public static String text2html(String s) {
HtmlBuffer buf = new HtmlBuffer();
buf.text(s);
return buf.toString();
}
}

View File

@@ -0,0 +1,15 @@
/*******************************************************************************
* Copyright (c) 2015 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.util;
public interface IRequestor<T> {
void accept(T node);
}

View File

@@ -0,0 +1,35 @@
/*******************************************************************************
* Copyright (c) 2015 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.util;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
public abstract class PrefixFinder {
public String getPrefix(IDocument doc, int offset, int lowerBound) {
try {
if (doc == null || offset > doc.getLength())
return null;
int prefixStart = offset;
while (prefixStart > lowerBound && isPrefixChar(doc.getChar(prefixStart-1))) {
prefixStart--;
}
return doc.get(prefixStart, offset-prefixStart);
} catch (BadLocationException e) {
return null;
}
}
public String getPrefix(IDocument doc, int offset) {
return getPrefix(doc, offset, 0);
}
protected abstract boolean isPrefixChar(char c);
}

View File

@@ -0,0 +1,34 @@
/*******************************************************************************
* Copyright (c) 2015 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.util;
/**
* Requestor that remembers only the last item received.
*
* @author Kris De Volder
*/
public class RememberLast<T> implements IRequestor<T> {
private T last = null;
@Override
public void accept(T node) {
this.last = node;
}
/**
* @return the last received item, may return null if no items where received.
*/
public T get() {
return last;
}
}

View File

@@ -0,0 +1,43 @@
/*******************************************************************************
* Copyright (c) 2016 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.util;
import javax.inject.Provider;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.texteditor.ITextEditor;
/**
* Convenience methods to addapt various types of objects representing a 'ui context'
* to a Shell{@link Provider}
*
* @author Kris De Volder
*/
public class ShellProviders {
public static Provider<Shell> from(final ITextEditor editor) {
return new Provider<Shell>() {
@Override
public Shell get() {
return editor.getSite().getShell();
}
};
}
public static Provider<Shell> from(final Composite composite) {
return new Provider<Shell>() {
@Override
public Shell get() {
return composite.getShell();
}
};
}
}

View File

@@ -0,0 +1,37 @@
/*******************************************************************************
* Copyright (c) 2017 Spring IDE Developers
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Spring IDE Developers - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.util;
import java.util.ArrayList;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Streams {
/**
* Returns the element in the stream, if the stream has exactly one element.
* Otherwise returns null
*/
public static <T> T getSingle(Stream<T> stream) {
ArrayList<T> els = stream
.limit(2) //Don't need more than 2 to know there is more than 1
.collect(Collectors.toCollection(() -> new ArrayList<>(2)));
return els.size()==1 ? els.get(0) : null;
}
/**
* Like java.util.Stream.of but returns Stream.empty if the element is null
*/
public static <T> Stream<T> fromNullable(T e) {
return e==null ? Stream.empty() : Stream.of(e);
}
}

View File

@@ -0,0 +1,24 @@
/*******************************************************************************
* Copyright (c) 2015 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.util;
import org.eclipse.core.resources.IContainer;
/**
* To make code that pops up dialogs and such more testable, this interface
* can be implemented providing either 'real' or 'mock' implementations.
*
* @author Kris De Volder
*/
public interface UserInteractions {
IContainer chooseOneSourceFolder(String title, String message, IContainer[] options, IContainer preferred);
void error(String title, String message);
}

View File

@@ -0,0 +1,88 @@
/*******************************************************************************
* Copyright (c) 2017 Spring IDE Developers
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Spring IDE Developers - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.util;
/**
* Exception if there is a failure when parsing a value. It does not wrap
* other exceptions such that when thrown, the parse exception is the "deepest"
* error.
*
*/
public class ValueParseException extends Exception {
private int startIndex = -1;
private int endIndex = -1;
private String highightString;
/**
*
*/
private static final long serialVersionUID = 1L;
public ValueParseException(String message) {
super(message);
}
public ValueParseException(String message, int startIndex, int endIndex, String highlightString) {
this(message, startIndex, endIndex);
this.highightString = highlightString;
}
public ValueParseException(String message, int startIndex, int endIndex) {
this(message);
this.startIndex = startIndex;
this.endIndex = endIndex;
}
public int getStartIndex() {
return startIndex;
}
public int getEndIndex() {
return endIndex;
}
public void adjustOffset(int by) {
if (startIndex>=0) {
this.startIndex += by;
}
if (endIndex>=0) {
this.endIndex += by;
}
}
public DocumentRegion getHighlightRegion(DocumentRegion containingRegion) {
int start = startIndex>=0 ? startIndex : 0;
int end = endIndex>=0 ? endIndex : containingRegion.length();
if (highightString!=null) {
//Make a 'best effort' adjusting start and end to highlight the correct string,
// even if positions are screwy because of handling escape sequences before parsing.
String actualHighlight = containingRegion.subSequence(start, end).toString();
if (!actualHighlight.equals(highightString)) {
String containingString = containingRegion.toString();
//Search 'close' to start position first
int found = containingString.indexOf(highightString, start);
if (found>=0) {
return containingRegion.subSequence(found, found+highightString.length());
}
//Second... search whole string
found = containingString.indexOf(highightString);
if (found>=0) {
return containingRegion.subSequence(found, found+highightString.length());
}
//Give up, couldn't find the highlight string... highlight everything
return containingRegion;
}
}
return containingRegion.subSequence(start, end);
}
}

View File

@@ -0,0 +1,30 @@
/*******************************************************************************
* Copyright (c) 2015-2017 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.util;
/**
* A ValueParser provides the means to Strings into some kind of
* value.
*
* @author Kris De Volder
*/
public interface ValueParser {
/**
* Parse the string and return its parsed representation.
* May either return null, or throw an {@link IllegalArgumentException} to indicate
* that the String is not the format this parser expects.
*/
Object parse(String str) throws Exception;
static ValueParser of(ValueParser x) {
return x;
}
}

View File

@@ -0,0 +1,59 @@
/*******************************************************************************
* Copyright (c) 2017 Spring IDE Developers
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Spring IDE Developers - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.util;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.core.runtime.Assert;
/**
* Constants and static methods to create generally useful value parsers.
*
* @author Kris De Volder
*/
public class ValueParsers {
public static final ValueParser NE_STRING = (s) -> {
if (!StringUtils.isBlank(s)) {
return s;
} else {
throw new ValueParseException("String should not be empty");
}
};
public static final ValueParser POS_INTEGER = integerRange(0, null);
public static ValueParser integerAtLeast(final Integer lowerBound) {
return integerRange(lowerBound, null);
}
public static ValueParser integerRange(final Integer lowerBound, final Integer upperBound) {
Assert.isLegal(lowerBound==null || upperBound==null || lowerBound <= upperBound);
return new ValueParser() {
@Override
public Object parse(String str) throws Exception {
int value = Integer.parseInt(str);
if (lowerBound!=null && value<lowerBound) {
if (lowerBound==0) {
throw new ValueParseException("Value must be positive");
} else {
throw new ValueParseException("Value must be at least "+lowerBound);
}
}
if (upperBound!=null && value>upperBound) {
throw new ValueParseException("Value must be at most "+upperBound);
}
return value;
}
};
}
}

View File

@@ -0,0 +1,108 @@
/*******************************************************************************
* Copyright (c) 2015 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.util;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentExtension4;
import org.springframework.ide.eclipse.editor.support.yaml.YamlDocument;
import com.google.common.base.Strings;
/**
* Helper methods to mainpulate indentation levels.
*
* @author Kris De Volder
*/
public class YamlIndentUtil {
/**
* Number of indentation levels (spaces) added between a child and parent.
* TODO: replace this constant by (existing!) yedit preference value
*/
public static final int INDENT_BY = 2;
public static final String INDENT_STR = Strings.repeat(" ", INDENT_BY);
/**
* Some functions introduce line separators and this may depend on the context (i.e. default line separator
* for the current document).
*/
public final String NEWLINE;
public YamlIndentUtil(String newline) {
this.NEWLINE = newline;
}
public YamlIndentUtil(YamlDocument doc) {
IDocument d = doc.getDocument();
if (d instanceof IDocumentExtension4) {
this.NEWLINE = ((IDocumentExtension4) d).getDefaultLineDelimiter();
} else {
this.NEWLINE = "\n"; //This shouldn't really happen.
}
}
/**
* Determine the 'known minimum' of two indentation levels. Correctly handle
* when either one or both indent levels are '-1' (unknown).
*/
public static int minIndent(int a, int b) {
if (a==-1) {
return b;
} else if (b==-1) {
return a;
} else {
return Math.min(a, b);
}
}
public static void addIndent(int indent, StringBuilder buf) {
for (int i = 0; i < indent; i++) {
buf.append(' ');
}
}
public void addNewlineWithIndent(int indent, StringBuilder buf) {
buf.append(NEWLINE);
addIndent(indent, buf);
}
public String newlineWithIndent(int indent) {
StringBuilder buf = new StringBuilder();
addNewlineWithIndent(indent, buf);
return buf.toString();
}
/**
* Applies a certain level of indentation to all new lines in the given text. Newlines
* are expressed by '\n' characters in the text will be replaced by the appropriate
* newline + indent.
* <p>
* Notes:
* - '\n' are replaced by the default line delimeter for the current document.
* - indentation is not applied to the first line of text.
*/
public String applyIndentation(String text, int indentBy) {
return text.replaceAll("\\n", newlineWithIndent(indentBy));
}
/**
* Increase offset by indentation. Take care when 'indent' is -1 (unkownn) to
* just return offset unmodified.
*/
public static int addToOffset(int offset, int indent) {
if (indent==-1) {
return offset;
}
return offset + indent;
}
}

View File

@@ -0,0 +1,40 @@
/*******************************************************************************
* Copyright (c) 2015, 2016 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.yaml;
import org.dadacoalition.yedit.editor.YEdit;
import org.eclipse.core.resources.IFile;
import org.eclipse.ui.IEditorInput;
import com.google.common.collect.ImmutableSet;
public abstract class AbstractYamlEditor extends YEdit {
private static final ImmutableSet<String> YAML_EXTENSIONS = ImmutableSet.of("yml", "yaml");
protected ImmutableSet<String> getSupportedFileExtensions() {
return YAML_EXTENSIONS;
}
@Override
protected boolean canHandleMove(IEditorInput originalElement, IEditorInput movedElement) {
//See https://issuetracker.springsource.com/browse/STS-4299
IFile file = (IFile)movedElement.getAdapter(IFile.class);
if (file!=null) {
String extension = file.getFileExtension();
if (extension!=null) {
extension = extension.toLowerCase();
return getSupportedFileExtensions().contains(extension);
}
}
return false;
}
}

View File

@@ -0,0 +1,243 @@
/*******************************************************************************
* Copyright (c) 2015, 2017 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.yaml;
import java.lang.reflect.Constructor;
import java.util.HashSet;
import java.util.Set;
import javax.inject.Provider;
import org.dadacoalition.yedit.editor.YEditSourceViewerConfiguration;
import org.dadacoalition.yedit.template.YEditCompletionProcessor;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextHover;
import org.eclipse.jface.text.ITextViewerExtension2;
import org.eclipse.jface.text.contentassist.ContentAssistant;
import org.eclipse.jface.text.contentassist.IContentAssistProcessor;
import org.eclipse.jface.text.contentassist.IContentAssistant;
import org.eclipse.jface.text.reconciler.IReconciler;
import org.eclipse.jface.text.reconciler.IReconcilingStrategy;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.DefaultAnnotationHover;
import org.eclipse.jface.text.source.IAnnotationHover;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Shell;
import org.springframework.ide.eclipse.editor.support.EditorSupportActivator;
import org.springframework.ide.eclipse.editor.support.ForceableReconciler;
import org.springframework.ide.eclipse.editor.support.completions.CompletionFactory;
import org.springframework.ide.eclipse.editor.support.completions.ICompletionEngine;
import org.springframework.ide.eclipse.editor.support.completions.ProposalProcessor;
import org.springframework.ide.eclipse.editor.support.hover.HoverInfoProvider;
import org.springframework.ide.eclipse.editor.support.hover.HoverInfoTextHover;
import org.springframework.ide.eclipse.editor.support.reconcile.DefaultQuickfixContext;
import org.springframework.ide.eclipse.editor.support.reconcile.QuickfixContext;
import org.springframework.ide.eclipse.editor.support.reconcile.ReconcileProblemAnnotationHover;
import org.springframework.ide.eclipse.editor.support.util.DefaultUserInteractions;
import org.springframework.ide.eclipse.editor.support.yaml.ast.YamlASTProvider;
import org.springframework.ide.eclipse.editor.support.yaml.hover.YamlHoverInfoProvider;
import org.springframework.ide.eclipse.editor.support.yaml.structure.YamlStructureProvider;
import org.yaml.snakeyaml.Yaml;
/**
* @author Kris De Volder
*/
public abstract class AbstractYamlSourceViewerConfiguration extends YEditSourceViewerConfiguration {
private static final Set<String> ANNOTIONS_SHOWN_IN_TEXT = new HashSet<>();
static {
ANNOTIONS_SHOWN_IN_TEXT.add("org.eclipse.jdt.ui.warning");
ANNOTIONS_SHOWN_IN_TEXT.add("org.eclipse.jdt.ui.error");
}
private static final Set<String> ANNOTIONS_SHOWN_IN_OVERVIEW_BAR = ANNOTIONS_SHOWN_IN_TEXT;
//TODO: the ANNOTIONS_SHOWN_IN_TEXT and ANNOTIONS_SHOWN_IN_OVERVIEW_BAR should be replaced with
// properly using preferences. An example of how to set this up can be found in the code
// of the Java properties file editor. Roughly these things need to happen:
// 1) use methods like 'isShownIntext' and 'isShownInOverviewRuler' which are defined in
// our super class.
// 2) initialize the super class with a preference store (simialr to how java properties file does it)
// 3) To be able to do 2) it is necessary to add a constructor to YEditSourceViewerConfiguration which
// accepts preference store and passes it to its super class. So this requires a patch to
// YEdit source code.
private Provider<Shell> shellProvider;
private final String DIALOG_SETTINGS_KEY = this.getClass().getName();
private final YamlASTProvider astProvider = new YamlASTProvider(new Yaml());
private YamlCompletionEngine completionEngine;
protected ForceableReconciler fReconciler;
public AbstractYamlSourceViewerConfiguration(Provider<Shell> shellProvider) {
this.shellProvider = shellProvider;
}
protected final IDialogSettings getDialogSettings() {
IDialogSettings dialogSettings = getPluginDialogSettings();
IDialogSettings existing = dialogSettings.getSection(DIALOG_SETTINGS_KEY);
if (existing!=null) {
return existing;
}
IDialogSettings created = dialogSettings.addNewSection(DIALOG_SETTINGS_KEY);
Point defaultPopupSize = getDefaultPopupSize();
if (defaultPopupSize!=null) {
int suggestW = defaultPopupSize.x;
int suggestH = defaultPopupSize.y;
created.put(ContentAssistant.STORE_SIZE_X, suggestW);
created.put(ContentAssistant.STORE_SIZE_Y, suggestH);
}
return created;
}
protected Point getDefaultPopupSize() {
return null;
}
protected abstract IDialogSettings getPluginDialogSettings();
@Override
public final IContentAssistant getContentAssistant(ISourceViewer viewer) {
IContentAssistant _a = super_getContentAssistant(viewer);
if (_a instanceof ContentAssistant) {
ContentAssistant a = (ContentAssistant)_a;
//IContentAssistProcessor processor = assistant.getContentAssistProcessor(IDocument.DEFAULT_CONTENT_TYPE);
//if (processor!=null) {
//TODO: don't overwrite existing processor but wrap it so
// we combine our proposals with existing propopals
//}
a.setInformationControlCreator(getInformationControlCreator(viewer));
a.enableColoredLabels(true);
a.enablePrefixCompletion(false);
a.enableAutoInsert(true);
a.enableAutoActivation(true);
a.setRestoreCompletionProposalSize(getDialogSettings());
ProposalProcessor processor = new ProposalProcessor(getCompletionEngine(viewer));
a.setContentAssistProcessor(processor, IDocument.DEFAULT_CONTENT_TYPE);
a.setSorter(CompletionFactory.SORTER);
}
return _a;
}
private IContentAssistant super_getContentAssistant(ISourceViewer sourceViewer) {
//Copied from superclass's getContentAssistant... then modifed to make ContentAssistant
// asynchronous.
ContentAssistant ca;
try {
//Use reflection to call the constructor because it only exists in Eclipse 4.7.
Constructor<ContentAssistant> constructor = ContentAssistant.class.getConstructor(boolean.class);
ca = constructor.newInstance(true);
} catch (Exception e) {
ca = new ContentAssistant();
}
IContentAssistProcessor cap = new YEditCompletionProcessor();
ca.setContentAssistProcessor(cap, IDocument.DEFAULT_CONTENT_TYPE);
ca.setInformationControlCreator(getInformationControlCreator(sourceViewer));
ca.enableAutoInsert(true);
return ca;
}
@Override
public ITextHover getTextHover(ISourceViewer sourceViewer, String contentType, int stateMask) {
if (contentType.equals(IDocument.DEFAULT_CONTENT_TYPE) && ITextViewerExtension2.DEFAULT_HOVER_STATE_MASK==stateMask) {
ITextHover delegate = getTextAnnotationHover(sourceViewer);
try {
HoverInfoProvider hoverProvider = getHoverProvider(sourceViewer);
if (hoverProvider!=null) {
return new HoverInfoTextHover(sourceViewer, getHoverProvider(sourceViewer), delegate);
}
} catch (Exception e) {
EditorSupportActivator.log(e);
}
return delegate;
} else {
return super.getTextHover(sourceViewer, contentType, stateMask);
}
}
public ICompletionEngine getCompletionEngine(ISourceViewer viewer) {
if (completionEngine==null) {
completionEngine = new YamlCompletionEngine(getStructureProvider(), getAssistContextProvider(viewer));
}
return completionEngine;
}
protected HoverInfoProvider getHoverProvider(ISourceViewer viewer) {
return new YamlHoverInfoProvider(getAstProvider(), getStructureProvider(), getAssistContextProvider(viewer));
}
protected final YamlASTProvider getAstProvider() {
return astProvider;
}
@Override
public final IReconciler getReconciler(ISourceViewer sourceViewer) {
if (fReconciler==null) {
fReconciler = createReconciler(sourceViewer);
}
return fReconciler;
}
@Override
public IAnnotationHover getAnnotationHover(ISourceViewer sourceViewer) {
return new DefaultAnnotationHover() {
@Override
protected boolean isIncluded(Annotation annotation) {
return ANNOTIONS_SHOWN_IN_OVERVIEW_BAR.contains(annotation.getType());
}
};
}
protected ITextHover getTextAnnotationHover(ISourceViewer sourceViewer) {
return new ReconcileProblemAnnotationHover(sourceViewer, getQuickfixContext(sourceViewer));
}
protected Shell getShell() {
return shellProvider.get();
}
protected final QuickfixContext getQuickfixContext(ISourceViewer sourceViewer) {
return new DefaultQuickfixContext(
getPluginId(),
getPreferencesStore(),
sourceViewer,
new DefaultUserInteractions(getShell())
);
}
protected abstract String getPluginId();
protected abstract IPreferenceStore getPreferencesStore();
protected abstract YamlStructureProvider getStructureProvider();
protected abstract YamlAssistContextProvider getAssistContextProvider(ISourceViewer viewer);
protected IReconcilingStrategy createReconcilerStrategy(ISourceViewer sourceViewer) {
return null;
}
protected ForceableReconciler createReconciler(ISourceViewer sourceViewer) {
//TODO: aplication.properties|yaml editors are overriding this. That should not be necessary.
IReconcilingStrategy strategy = createReconcilerStrategy(sourceViewer);
if (strategy!=null) {
ForceableReconciler reconciler = new ForceableReconciler(strategy);
reconciler.setDelay(500);
return reconciler;
}
return null;
}
}

View File

@@ -0,0 +1,24 @@
/*******************************************************************************
* Copyright (c) 2016 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.yaml;
import org.springframework.ide.eclipse.editor.support.yaml.completions.YamlAssistContext;
/**
* Defines a way to obtain a 'toplevel' {@link YamlAssistContext} for a given {@link YamlDocument}
*
* @author Kris De Volder
*/
public interface YamlAssistContextProvider {
YamlAssistContext getGlobalAssistContext(YamlDocument doc);
}

View File

@@ -0,0 +1,142 @@
/*******************************************************************************
* Copyright (c) 2016 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.yaml;
import java.util.Collection;
import java.util.Collections;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.springframework.ide.eclipse.editor.support.EditorSupportActivator;
import org.springframework.ide.eclipse.editor.support.completions.CompletionFactory;
import org.springframework.ide.eclipse.editor.support.completions.ICompletionEngine;
import org.springframework.ide.eclipse.editor.support.util.YamlIndentUtil;
import org.springframework.ide.eclipse.editor.support.yaml.completions.YamlAssistContext;
import org.springframework.ide.eclipse.editor.support.yaml.path.YamlPath;
import org.springframework.ide.eclipse.editor.support.yaml.structure.YamlStructureParser.SKeyNode;
import org.springframework.ide.eclipse.editor.support.yaml.structure.YamlStructureParser.SNode;
import org.springframework.ide.eclipse.editor.support.yaml.structure.YamlStructureParser.SNodeType;
import org.springframework.ide.eclipse.editor.support.yaml.structure.YamlStructureParser.SRootNode;
import org.springframework.ide.eclipse.editor.support.yaml.structure.YamlStructureParser.SSeqNode;
import org.springframework.ide.eclipse.editor.support.yaml.structure.YamlStructureProvider;
/**
* Abstract superclass to make it easier to define {@link ICompletionEngine} implementation
* for .yml file.
*
* @author Kris De Volder
*/
public class YamlCompletionEngine implements ICompletionEngine {
private final YamlAssistContextProvider contextProvider;
protected final YamlStructureProvider structureProvider;
public YamlCompletionEngine(YamlStructureProvider structureProvider, YamlAssistContextProvider contextProvider) {
Assert.isNotNull(structureProvider);
Assert.isNotNull(contextProvider);
this.structureProvider= structureProvider;
this.contextProvider = contextProvider;
}
protected final YamlAssistContext getGlobalContext(YamlDocument doc) {
return contextProvider.getGlobalAssistContext(doc);
}
protected CompletionFactory proposalFactory() {
return CompletionFactory.DEFAULT;
}
@Override
public Collection<ICompletionProposal> getCompletions(IDocument _doc, int offset) throws Exception {
YamlDocument doc = new YamlDocument(_doc, structureProvider);
if (!doc.isCommented(offset)) {
SRootNode root = doc.getStructure();
SNode current = root.find(offset);
YamlPath contextPath = getContextPath(doc, current, offset);
YamlAssistContext context = getContext(doc, offset, current, contextPath);
if (context==null && isDubiousKey(current, offset)) {
current = current.getParent();
contextPath = contextPath.dropLast();
context = getContext(doc, offset, current, contextPath);
}
if (context!=null) {
return context.getCompletions(doc, current, offset);
}
}
return Collections.emptyList();
}
/**
* A 'dubious' key is when the cursor is positioned right after a key's ':' character.
* This key is 'dubious' in that if the user would type a non-whitespace character next...
* then that key is no longer a key but parses as a 'value' instead.
*/
private boolean isDubiousKey(SNode node, int offset) {
if (node.getNodeType()==SNodeType.KEY) {
SKeyNode key = (SKeyNode)node;
return key.getColonOffset()+1==offset;
}
return false;
}
protected YamlAssistContext getContext(YamlDocument doc, int offset, SNode node, YamlPath contextPath) {
try {
return contextPath.traverse(getGlobalContext(doc));
} catch (Exception e) {
EditorSupportActivator.log(e);
return null;
}
}
protected YamlPath getContextPath(YamlDocument doc, SNode node, int offset) throws Exception {
if (node==null) {
return YamlPath.EMPTY;
} else if (node.getNodeType()==SNodeType.KEY) {
//slight complication. The area in the key and value of a key node represent different
// contexts for content assistance
SKeyNode keyNode = (SKeyNode)node;
if (keyNode.isInValue(offset)) {
return keyNode.getPath();
} else {
return keyNode.getParent().getPath();
}
} else if (node.getNodeType()==SNodeType.RAW) {
//Treat raw node as a 'key node'. This is basically assuming that is misclasified
// by structure parser because the ':' was not yet typed into the document.
//Complication: if line with cursor is empty or the cursor is inside the indentation
// area then the structure may not reflect correctly the context. This is because
// the correct context depends on text the user has not typed yet.(which will change the
// indentation level of the current line. So we must use the cursorIndentation
// rather than the structur-tree to determine the 'context' node.
int cursorIndent = doc.getColumn(offset);
int nodeIndent = node.getIndent();
int currentIndent = YamlIndentUtil.minIndent(cursorIndent, nodeIndent);
while (node.getIndent()==-1 || (node.getIndent()>=currentIndent && node.getNodeType()!=SNodeType.DOC)) {
node = node.getParent();
}
return node.getPath();
} else if (node.getNodeType()==SNodeType.SEQ) {
SSeqNode seqNode = (SSeqNode)node;
if (seqNode.isInValue(offset)) {
return seqNode.getPath();
} else {
return seqNode.getParent().getPath();
}
} else if (node.getNodeType()==SNodeType.DOC) {
return node.getPath();
} else {
throw new IllegalStateException("Missing case");
}
}
}

View File

@@ -0,0 +1,161 @@
/*******************************************************************************
* Copyright (c) 2015 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.yaml;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.springframework.ide.eclipse.editor.support.util.DocumentUtil;
import org.springframework.ide.eclipse.editor.support.yaml.structure.YamlStructureParser.SRootNode;
import org.springframework.ide.eclipse.editor.support.yaml.structure.YamlStructureProvider;
/**
* Wraps around a IDocument which is presumed to contain YML content and provides
* some utility methods for working with the contents of the document.
*
* @author Kris De Volder
*/
public class YamlDocument {
private IDocument doc;
private YamlStructureProvider structureProvider;
private SRootNode structure;
public YamlDocument(IDocument _doc, YamlStructureProvider structureProvider) {
this.doc = _doc;
this.structureProvider = structureProvider;
}
public IDocument getDocument() {
return doc;
}
public SRootNode getStructure() throws Exception {
if (this.structure==null) {
this.structure = structureProvider.getStructure(this);
}
return structure;
}
public int getLineOfOffset(int offset) throws BadLocationException {
return doc.getLineOfOffset(offset);
}
public IRegion getLineInformation(int line) throws BadLocationException {
return doc.getLineInformation(line);
}
public int getLineOffset(int line) throws BadLocationException {
return doc.getLineOffset(line);
}
/**
* Returns the number of leading spaces in front of a line. If the line is effectively empty (only contains
* comments and/or spaces then this returns -1 (meaning undefined, as indentation level only really means
* something for lines whuch have 'real' content.
*/
public int getLineIndentation(int line) {
IRegion r;
try {
r = getLineInformation(line);
} catch (BadLocationException e) {
//not a line in the document so it has no indentation
return -1;
}
int len = r.getLength();
int startOfLine = r.getOffset();
int leadingSpaces = 0;
while (leadingSpaces<len) {
char c = getChar(startOfLine+leadingSpaces);
if (c==' ') {
leadingSpaces++;
} else if (c=='#') {
return -1;
} else if (c!=' ') {
return leadingSpaces;
}
leadingSpaces++;
}
//Whole line scanned and nothing but spaces found
return -1;
}
public char getChar(int offset) {
try {
return doc.getChar(offset);
} catch (BadLocationException e) {
return 0;
}
}
/**
* Determine whether given offset is inside a comment.
*/
public boolean isCommented(int offset) throws Exception {
//Yaml only has end of line comments marked with a '#'.
//So comments never span multiple lines of text and we only have scan back
//from offset upto the start of the current line.
IRegion lineInfo = doc.getLineInformationOfOffset(offset);
int startOfLine = lineInfo.getOffset();
while (offset>=startOfLine) {
char c = getChar(offset);
if (c=='#') {
return true;
}
offset--;
}
return false;
}
/**
* Fetch text between two offsets. Doesn't throw BadLocationException.
* If either one or both of the offsets points outside the
* document then they will be adjusted to point the appropriate boundary to
* retrieve the text just upto the end or beginning of the document instead.
*/
public String textBetween(int start, int end) {
return DocumentUtil.textBetween(doc, start, end);
}
public int getColumn(int offset) throws Exception {
IRegion r = doc.getLineInformationOfOffset(offset);
return offset - r.getOffset();
}
/**
* Fetct text between a given offset and the start of the line that
* offset belongs to.
*/
public String getLineTextBefore(int offset) throws Exception {
IRegion l = doc.getLineInformationOfOffset(offset);
return textBetween(l.getOffset(), offset);
}
/**
* Fetch the text of the line at a given offset (i.e. all text extending from
* offset to the beginning and end of line)
*/
public String getLineTextAtOffset(int offset) throws Exception {
IRegion l = doc.getLineInformationOfOffset(offset);
return textBetween(l.getOffset(), l.getOffset()+l.getLength());
}
public int getStartOfLineAtOffset(int offset) throws Exception {
return doc.getLineInformationOfOffset(offset).getOffset();
}
@Override
public String toString() {
return "YamlDocument(>>>>\n"+getDocument().get()+"\n<<<<)";
}
}

View File

@@ -0,0 +1,159 @@
/**
* Copyright (c) 2008, 2019 https://www.snakeyaml.org and others.
*
* 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
*
* https://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.
*
* Contributors:
*
* 2008 - https://www.snakeyaml.org original api and implementation.
* 2019 - Copied and modified by Pivotal.
*/
package org.springframework.ide.eclipse.editor.support.yaml.ast;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.ide.eclipse.editor.support.reconcile.IProblemCollector;
import org.springframework.ide.eclipse.editor.support.yaml.reconcile.YamlSchemaProblems;
import org.yaml.snakeyaml.nodes.MappingNode;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.nodes.NodeTuple;
import org.yaml.snakeyaml.nodes.SequenceNode;
import org.yaml.snakeyaml.nodes.Tag;
/**
* Bits and pieces copied from snakeyaml library to support dealing with '<<' merge
* nodes.
* <p>
* Some modifications made to support our own use-cases.
*/
public class NodeMergeSupport {
private IProblemCollector problems;
public NodeMergeSupport(IProblemCollector problems) {
this.problems = problems;
}
/**
* Walk the entire tree from given root and deal with any '<<:'
* entries found by flattening / merging them into the parent node.
*
* @param root
*/
public void mergeAll(Node root) {
mergeAll(root, new HashSet<>());
}
private void mergeAll(Node root, Set<Node> seen) {
if (seen.add(root)) {
if (root instanceof SequenceNode) {
SequenceNode seq = (SequenceNode) root;
for (Node node : seq.getValue()) {
mergeAll(node, seen);
}
} else if (root instanceof MappingNode) {
MappingNode map = (MappingNode) root;
flattenMapping((MappingNode) root);
for (NodeTuple entry : map.getValue()) {
mergeAll(entry.getValueNode(), seen);
}
}
}
}
public void flattenMapping(MappingNode node) {
// perform merging only on nodes containing merge node(s)
//processDuplicateKeys(node);
if (node.isMerged()) {
node.setValue(mergeNode(node, true, new HashMap<Object, Integer>(),
new ArrayList<NodeTuple>()));
node.setMerged(false);
}
}
/**
* Does merge for supplied mapping node.
*
* @param node
* where to merge
* @param isPreffered
* true if keys of node should take precedence over others...
* @param key2index
* maps already merged keys to index from values
* @param values
* collects merged NodeTuple
* @return list of the merged NodeTuple (to be set as value for the
* MappingNode)
*/
private List<NodeTuple> mergeNode(MappingNode node, boolean isPreffered,
Map<Object, Integer> key2index, List<NodeTuple> values) {
Iterator<NodeTuple> iter = node.getValue().iterator();
while (iter.hasNext()) {
final NodeTuple nodeTuple = iter.next();
final Node keyNode = nodeTuple.getKeyNode();
final Node valueNode = nodeTuple.getValueNode();
if (keyNode.getTag().equals(Tag.MERGE)) {
iter.remove();
switch (valueNode.getNodeId()) {
case mapping:
MappingNode mn = (MappingNode) valueNode;
mergeNode(mn, false, key2index, values);
break;
case sequence:
SequenceNode sn = (SequenceNode) valueNode;
List<Node> vals = sn.getValue();
for (Node subnode : vals) {
if (!(subnode instanceof MappingNode)) {
problems.accept(YamlSchemaProblems.schemaProblem(
"Expected a mapping for merging, but found "+subnode.getNodeId(), subnode
));
} else {
MappingNode mnode = (MappingNode) subnode;
mergeNode(mnode, false, key2index, values);
}
}
break;
default:
problems.accept(YamlSchemaProblems.schemaProblem(
"Expected a mapping or list of mappings for merging, but found "
+ valueNode.getNodeId(),
valueNode
));
}
} else {
// we need to construct keys to avoid duplications
String key = NodeUtil.asScalar(keyNode);
if (key!=null) {
if (!key2index.containsKey(key)) { // 1st time merging key
values.add(nodeTuple);
// keep track where tuple for the key is
key2index.put(key, values.size() - 1);
} else if (isPreffered) { // there is value for the key, but we
// need to override it
// change value for the key using saved position
values.set(key2index.get(key), nodeTuple);
}
}
}
}
return values;
}
}

View File

@@ -0,0 +1,200 @@
/*******************************************************************************
* Copyright (c) 2015 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.yaml.ast;
import org.yaml.snakeyaml.nodes.MappingNode;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.nodes.NodeTuple;
import org.yaml.snakeyaml.nodes.SequenceNode;
/**
* A node reference represents a 'pointer' to a location where a Node is stored.
* The concept is useful because when looking for a 'path' inside an Yaml AST a
* NodeRef makes it explicit where the node is with respect to some 'container'.
* For example it allows distinguishing between a reference to Node which is
* obtained from map key versus a map value.
*
* @author Kris De Volder
*/
public abstract class NodeRef<Parent> {
public static enum Kind {
ROOT, SEQ, KEY, VAL
}
private Parent parent;
public NodeRef(Parent parent) {
this.parent = parent;
}
public Parent getParent() {
return parent;
}
public abstract Node get();
public abstract void put(Node value);
public abstract String toString();
public abstract Kind getKind();
/**
* Represents a reference to a root node, contained directly
* inside a {@link YamlFileAST}
*/
public static class RootRef extends NodeRef<YamlFileAST> {
private int index;
public RootRef(YamlFileAST file, int index) {
super(file);
this.index = index;
}
@Override
public Node get() {
return getParent().get(index);
}
@Override
public void put(Node value) {
getParent().put(index, value);
}
@Override
public String toString() {
return "ROOT["+index+"]";
}
@Override
public Kind getKind() {
return Kind.ROOT;
}
public int getIndex() {
return index;
}
}
public static class SeqRef extends NodeRef<SequenceNode> {
private int index;
public SeqRef(SequenceNode seq, int index) {
super(seq);
this.index = index;
}
@Override
public Node get() {
return getParent().getValue().get(index);
}
@Override
public void put(Node value) {
getParent().getValue().set(index, value);
}
@Override
public String toString() {
return "["+index+"]";
}
@Override
public Kind getKind() {
return Kind.SEQ;
}
public int getIndex() {
return index;
}
}
/**
* Abstract, represent reference to either a key or
* value inside a map tuple. Concrete subclasses define whether
* key or value is being accessed.
*/
public static abstract class TupleRef extends NodeRef<MappingNode> {
protected int index;
public TupleRef(MappingNode map, int index) {
super(map);
this.index = index;
}
public NodeTuple getTuple() {
return getParent().getValue().get(index);
}
public void putTuple(NodeTuple value) {
getParent().getValue().set(index, value);
}
}
/**
* References a key of a map entry
*/
public static class TupleKeyRef extends TupleRef {
public TupleKeyRef(MappingNode parent, int index) {
super(parent, index);
}
@Override
public Node get() {
return getTuple().getKeyNode();
}
@Override
public void put(Node newKey) {
NodeTuple tuple = getTuple();
putTuple(new NodeTuple(newKey, tuple.getValueNode()));
}
@Override
public String toString() {
return "@key["+index+"]";
}
@Override
public Kind getKind() {
return Kind.KEY;
}
}
/**
* References a value of a map entry
*/
public static class TupleValueRef extends TupleRef {
public TupleValueRef(MappingNode parent, int index) {
super(parent, index);
}
@Override
public Node get() {
return getTuple().getValueNode();
}
@Override
public void put(Node newValue) {
NodeTuple t = getTuple();
putTuple(new NodeTuple(t.getKeyNode(), newValue));
}
@Override
public String toString() {
String keyString = NodeUtil.asScalar(getTuple().getKeyNode());
if (keyString!=null) {
//more readable to use the key value than the index of the tuple
return "@val['"+keyString+"']";
}
return "@val["+index+"]";
}
@Override
public Kind getKind() {
return Kind.VAL;
}
public Node getKey() {
return getTuple().getKeyNode();
}
}
}

View File

@@ -0,0 +1,135 @@
/*******************************************************************************
* Copyright (c) 2016-2017 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.yaml.ast;
import java.util.Collections;
import java.util.Set;
import org.eclipse.jface.text.IDocument;
import org.springframework.ide.eclipse.editor.support.util.DocumentRegion;
import org.yaml.snakeyaml.nodes.MappingNode;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.nodes.NodeId;
import org.yaml.snakeyaml.nodes.NodeTuple;
import org.yaml.snakeyaml.nodes.ScalarNode;
import org.yaml.snakeyaml.nodes.SequenceNode;
import com.google.common.collect.ImmutableSet;
/**
* @author Kris De Volder
*/
public class NodeUtil {
/**
* Determines whether a node contains the given offset.
* The range a node covers between its begin and end mark is treated
* as a half open interval. The start offset is treated as included
* in the range but the end offset is excluded.
* <p>
* This is to avoid ambiguity as node ranges tend to 'join'
* together so that the end region of one node coincides with
* the start region of the next node. By treating node ranges
* as 'half open' intervals every offset is typically only
* part of two different nodes if those nodes effectively
* have overlapping ranges (i.e. only if one node contains
* the other). Thus, an operation like finding the smallest
* node that contains an offset is unambgious.
*/
public static boolean contains(Node node, int offset) {
return getStart(node)<=offset && offset<getEnd(node);
}
public static int getStart(Node node) {
return node.getStartMark().getIndex();
}
public static int getEnd(Node node) {
return node.getEndMark().getIndex();
}
/**
* Retrieve String value of a scalar node.
* @return String value or null if node is not a Scalar node.
*/
public static String asScalar(Node node) {
if (node!=null && node.getNodeId()==NodeId.scalar) {
return ((ScalarNode)node).getValue();
}
return null;
}
public static MappingNode asMapping(Node node) {
if (node!=null && node.getNodeId()==NodeId.mapping) {
return (MappingNode) node;
}
return null;
}
public static SequenceNode asSequence(Node node) {
if (node!=null && node.getNodeId()==NodeId.sequence) {
return (SequenceNode) node;
}
return null;
}
/**
* Get the scalar values of all keys of the given {@link MappingNode} as Strings.
* Any non-scalar keys are silently ignored.
*/
public static Set<String> getScalarKeys(Node node) {
if (node instanceof MappingNode) {
MappingNode mapNode = (MappingNode) node;
ImmutableSet.Builder<String> builder = ImmutableSet.builder();
for (NodeTuple entry : mapNode.getValue()) {
String key = NodeUtil.asScalar(entry.getKeyNode());
if (key!=null) { //key not a scalar? => something funky so skip it
builder.add(key);
}
}
return builder.build();
}
return Collections.emptySet();
}
public static String getScalarProperty(Node node, String propName) {
if (node instanceof MappingNode) {
for (NodeTuple entry : ((MappingNode)node).getValue()) {
String key = NodeUtil.asScalar(entry.getKeyNode());
if (propName.equals(key)) {
String value = asScalar(entry.getValueNode());
if (value!=null) {
return value;
}
}
}
}
return null;
}
public static Node getProperty(Node node, String propName) {
if (node instanceof MappingNode) {
for (NodeTuple entry : ((MappingNode)node).getValue()) {
String key = NodeUtil.asScalar(entry.getKeyNode());
if (propName.equals(key)) {
return entry.getValueNode();
}
}
}
return null;
}
public static DocumentRegion region(IDocument doc, Node node) {
return new DocumentRegion(doc, node.getStartMark().getIndex(), node.getEndMark().getIndex());
}
}

View File

@@ -0,0 +1,87 @@
/*******************************************************************************
* Copyright (c) 2015, 2016 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.yaml.ast;
import java.io.StringReader;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentListener;
import org.yaml.snakeyaml.Yaml;
/**
* Responsible for providing a AST for a document containing yml formatted text.
*
* @author Kris De Volder
*/
public class YamlASTProvider {
private YamlFileAST cached = null;
/**
* Tracks the source from which cached AST got parsed.
*/
private IDocument cachedFor = null;
/**
* For cache invalidation
*/
private IDocumentListener listener = new IDocumentListener() {
public void documentChanged(final DocumentEvent event) {
Job job = new Job("Clear YamlASTProvider Cache") {
protected IStatus run(IProgressMonitor monitor) {
changed(event.getDocument());
return Status.OK_STATUS;
}
};
job.setPriority(Job.INTERACTIVE);
job.setSystem(true);
job.schedule();
}
public void documentAboutToBeChanged(DocumentEvent event) {
//Don't care.
}
};
private Yaml yaml;
public YamlASTProvider(Yaml yaml) {
this.yaml = yaml;
}
private synchronized void changed(IDocument doc) {
if (doc==cachedFor) {
doc.removeDocumentListener(listener);
cachedFor = null;
cached = null;
}
}
public synchronized YamlFileAST getAST(IDocument doc) {
if (doc==cachedFor) {
return cached;
} else {
if (cachedFor!=null) {
cachedFor.removeDocumentListener(listener);
}
doc.addDocumentListener(listener);
cached = new YamlFileAST(doc, yaml.composeAll(new StringReader(doc.get())));
cachedFor = doc;
}
return cached;
}
}

View File

@@ -0,0 +1,161 @@
/*******************************************************************************
* Copyright (c) 2015 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.yaml.ast;
import static org.springframework.ide.eclipse.editor.support.yaml.ast.NodeUtil.contains;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.eclipse.jface.text.IDocument;
import org.springframework.ide.eclipse.editor.support.util.Collector;
import org.springframework.ide.eclipse.editor.support.util.IRequestor;
import org.springframework.ide.eclipse.editor.support.util.RememberLast;
import org.springframework.ide.eclipse.editor.support.yaml.ast.NodeRef.RootRef;
import org.springframework.ide.eclipse.editor.support.yaml.ast.NodeRef.SeqRef;
import org.springframework.ide.eclipse.editor.support.yaml.ast.NodeRef.TupleKeyRef;
import org.springframework.ide.eclipse.editor.support.yaml.ast.NodeRef.TupleValueRef;
import org.yaml.snakeyaml.nodes.MappingNode;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.nodes.SequenceNode;
/**
* Represents a parsed yml file.
*
* @author Kris De Volder
*/
public class YamlFileAST {
private final IDocument doc;
private static final List<NodeRef<?>> NO_CHILDREN = Collections.emptyList();
private List<Node> nodes;
public YamlFileAST(IDocument doc, Iterable<Node> iter) {
this.doc = doc;
nodes = new ArrayList<>();
for (Node node : iter) {
nodes.add(node);
}
}
public List<NodeRef<?>> findPath(int offset) {
Collector<NodeRef<?>> path = new Collector<>();
findPath(offset, path);
return path.get();
}
/**
* Find 'smallest' ast node that contains offset. The pathRequestor will
* be called as the search progresses down the AST on all nodes on the
* path to the smallest node. If no node in the tree contains the offset
* the requestor will not be called at all.
*/
public void findPath(int offset, IRequestor<NodeRef<?>> pathRequestor) {
for (int i = 0; i < nodes.size(); i++) {
Node node = nodes.get(i);
if (contains(node, offset)) {
pathRequestor.accept(new RootRef(this, i) );
findPath(node, offset, pathRequestor);
return;
}
}
}
/**
* Find smallest node that is a child of 'n' that contains 'offset'. Each visited
* node containing the offset, from the down to the found node are
* passed to the pathRequestor.
*/
private void findPath(Node n, int offset, IRequestor<NodeRef<?>> pathRequestor) {
//TODO: avoid lots of garbage production by not using 'getChildren'
// but inling getChildren (i.e a switch-case that visits
// the children without putting them into temporary collections.)
// By doing this it should be possible to avoid creaing lots of temporary
// array lists and NodeRef objects and only create NodeRef objects for
// the nodes we actually care about (i.e. the ones on the path).
List<NodeRef<?>> children = getChildren(n);
for (int i = 0; i < children.size(); i++) {
NodeRef<?> c = children.get(i);
if (contains(c.get(), offset)) {
pathRequestor.accept(c);
findPath(c.get(), offset, pathRequestor);
return;
}
}
}
public static List<NodeRef<?>> getChildren(Node n) {
switch (n.getNodeId()) {
case scalar:
return NO_CHILDREN;
case sequence:
return getChildren((SequenceNode)n);
case mapping:
return getChildren((MappingNode)n);
case anchor:
//TODO: is this right? maybe we should visit down into 'realnode'
// but do we then potentially visit the same node twice?
return NO_CHILDREN;
}
return null;
}
public List<Node> getNodes() {
return nodes;
}
private static List<NodeRef<?>> getChildren(SequenceNode seq) {
int nodes = seq.getValue().size();
ArrayList<NodeRef<?>> children = new ArrayList<>(nodes);
for (int i = 0; i < nodes; i++) {
children.add(new SeqRef(seq, i));
}
return children;
}
private static List<NodeRef<?>> getChildren(MappingNode map) {
int entries = map.getValue().size();
ArrayList<NodeRef<?>> children = new ArrayList<>(entries*2);
for (int i = 0; i < entries; i++) {
children.add(new TupleKeyRef(map, i));
children.add(new TupleValueRef(map, i));
}
return children;
}
public NodeRef<?> findNodeRef(int offset) {
RememberLast<NodeRef<?>> lastNode = new RememberLast<>();
findPath(offset, lastNode);
return lastNode.get();
}
public Node findNode(int offset) {
NodeRef<?> ref = findNodeRef(offset);
if (ref!=null) {
return ref.get();
}
return null;
}
public Node get(int index) {
return nodes.get(index);
}
public void put(int index, Node value) {
nodes.set(index, value);
}
public IDocument getDocument() {
return doc;
}
}

View File

@@ -0,0 +1,102 @@
/*******************************************************************************
* Copyright (c) 2015, 2016 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.yaml.completions;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.contentassist.ICompletionProposalExtension3;
import org.eclipse.jface.text.contentassist.ICompletionProposalExtension4;
import org.eclipse.jface.text.contentassist.ICompletionProposalExtension6;
import org.eclipse.jface.text.contentassist.IContextInformation;
import org.eclipse.jface.viewers.StyledString;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.springframework.ide.eclipse.editor.support.EditorSupportActivator;
import org.springframework.ide.eclipse.editor.support.completions.CompletionFactory.ScoreableProposal;
import org.springframework.ide.eclipse.editor.support.completions.ProposalApplier;
import org.springframework.ide.eclipse.editor.support.yaml.schema.YType;
public abstract class AbstractPropertyProposal extends ScoreableProposal implements ICompletionProposalExtension3,
ICompletionProposalExtension4, ICompletionProposalExtension6
{
protected final IDocument fDoc;
private final ProposalApplier proposalApplier;
private boolean isDeprecated = false;
public AbstractPropertyProposal(IDocument doc, ProposalApplier applier) {
this.proposalApplier = applier;
this.fDoc = doc;
}
public Point getSelection(IDocument document) {
try {
return proposalApplier.getSelection(document);
} catch (Exception e) {
EditorSupportActivator.log(e);
return null;
}
}
public String getDisplayString() {
StyledString styledText = getStyledDisplayString();
return styledText.getString();
}
public Image getImage() {
return null;
}
public IContextInformation getContextInformation() {
return null;
}
@Override
public StyledString getStyledDisplayString() {
StyledString result = new StyledString();
result = result.append(super.getStyledDisplayString());
YType type = getType();
if (type!=null) {
String typeStr = niceTypeName(type);
result.append(" : "+typeStr, StyledString.DECORATIONS_STYLER);
}
return result;
}
protected boolean isDeprecated() {
return isDeprecated;
}
public void deprecate() {
if (!isDeprecated()) {
deemphasize();
deemphasize();
isDeprecated = true;
}
}
protected abstract YType getType();
protected abstract String getHighlightPattern();
protected abstract String getBaseDisplayString();
protected abstract String niceTypeName(YType type);
@Override
public String toString() {
return getBaseDisplayString();
}
@Override
public void apply(IDocument document) {
try {
proposalApplier.apply(document);
} catch (Exception e) {
EditorSupportActivator.log(e);
}
}
}

View File

@@ -0,0 +1,113 @@
/*******************************************************************************
* Copyright (c) 2016 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.yaml.completions;
import org.springframework.ide.eclipse.editor.support.completions.CompletionFactory;
import org.springframework.ide.eclipse.editor.support.hover.HoverInfo;
import org.springframework.ide.eclipse.editor.support.util.DocumentRegion;
import org.springframework.ide.eclipse.editor.support.util.PrefixFinder;
import org.springframework.ide.eclipse.editor.support.yaml.YamlDocument;
import org.springframework.ide.eclipse.editor.support.yaml.path.YamlPath;
import org.springframework.ide.eclipse.editor.support.yaml.structure.YamlStructureParser.SDocNode;
import org.springframework.ide.eclipse.editor.support.yaml.structure.YamlStructureParser.SKeyNode;
import org.springframework.ide.eclipse.editor.support.yaml.structure.YamlStructureParser.SNode;
import org.springframework.ide.eclipse.editor.support.yaml.structure.YamlStructureParser.SNodeType;
import org.springframework.ide.eclipse.editor.support.yaml.structure.YamlStructureParser.SRootNode;
/**
* @author Kris De Volder
*/
public abstract class AbstractYamlAssistContext implements YamlAssistContext {
/**
* Delete a content assist query from the document, and also the line of
* text in the document that contains it, if that line of text contains just the
* query surrounded by whitespace.
*/
public static void deleteQueryAndLine(YamlDocument doc, String query, int queryOffset, YamlPathEdits edits) throws Exception {
edits.delete(queryOffset, query);
String wholeLine = doc.getLineTextAtOffset(queryOffset);
if (wholeLine.trim().equals(query.trim())) {
edits.deleteLineBackwardAtOffset(queryOffset);
}
}
public final int documentSelector;
public final YamlPath contextPath;
private static PrefixFinder prefixfinder = new PrefixFinder() {
protected boolean isPrefixChar(char c) {
return !Character.isWhitespace(c);
}
};
protected final String getPrefix(YamlDocument doc, SNode node, int offset) {
//For value completions... in general we would like to determine the whole text
// corresponding to the value, so a simplistic backwards scan isn't good enough.
// instead we should use offset in current node / structure to determine the
// the start of the current value.
if (node.getNodeType()==SNodeType.KEY) {
SKeyNode keyNode = (SKeyNode) node;
if (keyNode.isInValue(offset)) {
int valueStart = keyNode.getColonOffset()+1;
while (valueStart<=offset && Character.isWhitespace(doc.getChar(valueStart))) {
valueStart++;
}
if (offset>=valueStart) {
return doc.textBetween(valueStart, offset);
} else {
//only whitespace, or nothing found upto the cursor
return "";
}
}
// } else if (node.getNodeType()==SNodeType.RAW) {
// TODO: Handle this as we could be in a value that's on the next line instead of right behind the node
}
//If not one of the special cases where we try to be more precise...
// we use simplistic backward scan to determine 'CA query'.
return prefixfinder.getPrefix(doc.getDocument(), offset);
}
public AbstractYamlAssistContext(int documentSelector, YamlPath contextPath) {
this.documentSelector = documentSelector;
this.contextPath = contextPath;
}
protected SNode getContextNode(YamlDocument file) throws Exception {
return contextPath.traverse((SNode)getContextRoot(file));
}
protected SDocNode getContextRoot(YamlDocument file) throws Exception {
SRootNode root = file.getStructure();
return (SDocNode) root.getChildren().get(documentSelector);
}
protected CompletionFactory completionFactory() {
return CompletionFactory.DEFAULT;
}
@Override
public HoverInfo getHoverInfo() {
return null;
}
@Override
public HoverInfo getValueHoverInfo(YamlDocument doc, DocumentRegion documentRegion) {
//By default we don't provide value-specific hover, so just show the same hover
// as the assistContext the value is in. This is likely more interesting than showing nothing at all.
return getHoverInfo();
}
}

View File

@@ -0,0 +1,39 @@
/*******************************************************************************
* Copyright (c) 2016 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.yaml.completions;
import org.springframework.ide.eclipse.editor.support.yaml.YamlAssistContextProvider;
import org.springframework.ide.eclipse.editor.support.yaml.YamlDocument;
import org.springframework.ide.eclipse.editor.support.yaml.schema.YamlSchema;
/**
* A {@link YamlAssistContextProvider} that creates {@link YamlAssistContext}s from {@link YamlSchema}
*
* @author Kris De Volder
*/
public class SchemaBasedYamlAssistContextProvider implements YamlAssistContextProvider {
private YamlSchema schema;
public SchemaBasedYamlAssistContextProvider(YamlSchema schema) {
this.schema = schema;
}
@Override
public YamlAssistContext getGlobalAssistContext(YamlDocument doc) {
return new TopLevelAssistContext() {
@Override
protected YamlAssistContext getDocumentContext(int documentSelector) {
return new YTypeAssistContext(this, documentSelector, schema.getTopLevelType(), schema.getTypeUtil());
}
};
}
}

View File

@@ -0,0 +1,69 @@
/*******************************************************************************
* Copyright (c) 2015, 2016 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.yaml.completions;
import java.util.Collection;
import java.util.Collections;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.springframework.ide.eclipse.editor.support.hover.HoverInfo;
import org.springframework.ide.eclipse.editor.support.util.DocumentRegion;
import org.springframework.ide.eclipse.editor.support.yaml.YamlDocument;
import org.springframework.ide.eclipse.editor.support.yaml.path.YamlPathSegment;
import org.springframework.ide.eclipse.editor.support.yaml.structure.YamlStructureParser.SNode;
/**
* Abstract YamlAssistContext for the toplevel of a YamlDocument.
* <p>
* This context is typically a kind of 'dummy' context that is not
* used to generate completions (it is not possible to type anything
* in this context because... Wherever you type, you are always implicitly
* typing in one of the 'subdocuments' of the YamlFile.
* <p>
* All this context needs to be able to do therefore, is to support traversal so that
* it selects the appropriate context for a subdocument.
*
* @author Kris De Volder
*/
public abstract class TopLevelAssistContext implements YamlAssistContext {
@Override
public YamlAssistContext traverse(YamlPathSegment s) throws Exception {
Integer documentSelector = s.toIndex();
if (documentSelector!=null) {
return getDocumentContext(documentSelector);
}
return null;
}
@Override
public Collection<ICompletionProposal> getCompletions(YamlDocument doc, SNode node, int offset) throws Exception {
//This context really should never be used directly to create completions. But we provide
// a dummy implementation anyway.
return Collections.emptyList();
}
@Override
public HoverInfo getHoverInfo() {
return null;
}
public HoverInfo getHoverInfo(YamlPathSegment lastSegment) {
return null;
}
@Override
public HoverInfo getValueHoverInfo(YamlDocument doc, DocumentRegion documentRegion) {
return null;
}
protected abstract YamlAssistContext getDocumentContext(int documentSelector);
}

View File

@@ -0,0 +1,248 @@
/*******************************************************************************
* Copyright (c) 2016 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.yaml.completions;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.springframework.ide.eclipse.editor.support.EditorSupportActivator;
import org.springframework.ide.eclipse.editor.support.completions.DocumentEdits;
import org.springframework.ide.eclipse.editor.support.hover.HoverInfo;
import org.springframework.ide.eclipse.editor.support.hover.YPropertyHoverInfo;
import org.springframework.ide.eclipse.editor.support.util.CollectionUtil;
import org.springframework.ide.eclipse.editor.support.util.FuzzyMatcher;
import org.springframework.ide.eclipse.editor.support.util.YamlIndentUtil;
import org.springframework.ide.eclipse.editor.support.yaml.YamlDocument;
import org.springframework.ide.eclipse.editor.support.yaml.path.YamlPath;
import org.springframework.ide.eclipse.editor.support.yaml.path.YamlPathSegment;
import org.springframework.ide.eclipse.editor.support.yaml.path.YamlPathSegment.YamlPathSegmentType;
import org.springframework.ide.eclipse.editor.support.yaml.schema.YType;
import org.springframework.ide.eclipse.editor.support.yaml.schema.YTypeUtil;
import org.springframework.ide.eclipse.editor.support.yaml.schema.YTypedProperty;
import org.springframework.ide.eclipse.editor.support.yaml.schema.YValueHint;
import org.springframework.ide.eclipse.editor.support.yaml.structure.YamlStructureParser.SChildBearingNode;
import org.springframework.ide.eclipse.editor.support.yaml.structure.YamlStructureParser.SKeyNode;
import org.springframework.ide.eclipse.editor.support.yaml.structure.YamlStructureParser.SNode;
public class YTypeAssistContext extends AbstractYamlAssistContext {
final private YTypeUtil typeUtil;
final private YType type;
final private YamlAssistContext parent;
public YTypeAssistContext(YTypeAssistContext parent, YamlPath contextPath, YType YType, YTypeUtil typeUtil) {
super(parent.documentSelector, contextPath);
this.parent = parent;
this.type = YType;
this.typeUtil = typeUtil;
}
public YTypeAssistContext(TopLevelAssistContext parent, int documentSelector, YType type, YTypeUtil typeUtil) {
super(documentSelector, YamlPath.EMPTY);
this.type = type;
this.typeUtil = typeUtil;
this.parent = parent;
}
@Override
public Collection<ICompletionProposal> getCompletions(YamlDocument doc, SNode node, int offset) throws Exception {
String query = getPrefix(doc, node, offset);
List<ICompletionProposal> valueCompletions = getValueCompletions(doc, offset, query);
if (!valueCompletions.isEmpty()) {
return valueCompletions;
}
return getKeyCompletions(doc, offset, query);
}
public List<ICompletionProposal> getKeyCompletions(YamlDocument doc, int offset, String query) throws Exception {
int queryOffset = offset - query.length();
List<YTypedProperty> properties = typeUtil.getProperties(type);
if (CollectionUtil.hasElements(properties)) {
ArrayList<ICompletionProposal> proposals = new ArrayList<>(properties.size());
SNode contextNode = getContextNode(doc);
Set<String> definedProps = getDefinedProperties(contextNode);
for (YTypedProperty p : properties) {
String name = p.getName();
double score = FuzzyMatcher.matchScore(query, name);
if (score!=0) {
YamlPath relativePath = YamlPath.fromSimpleProperty(name);
YamlPathEdits edits = new YamlPathEdits(doc);
if (!definedProps.contains(name)) {
//property not yet defined
YType YType = p.getType();
edits.delete(queryOffset, query);
edits.createPathInPlace(contextNode, relativePath, queryOffset, appendTextFor(YType));
proposals.add(completionFactory().beanProperty(doc.getDocument(),
contextPath.toPropString(), getType(),
query, p, score, edits, typeUtil)
);
} else {
//property already defined
// instead of filtering, navigate to the place where its defined.
deleteQueryAndLine(doc, query, queryOffset, edits);
//Cast to SChildBearingNode cannot fail because otherwise definedProps would be the empty set.
edits.createPath((SChildBearingNode) contextNode, relativePath, "");
proposals.add(
completionFactory().beanProperty(doc.getDocument(),
contextPath.toPropString(), getType(),
query, p, score, edits, typeUtil)
.deemphasize() //deemphasize because it already exists
);
}
}
}
return proposals;
}
return Collections.emptyList();
}
/**
* Computes the text that should be appended at the end of a completion
* proposal depending on what type of value is expected.
*/
protected String appendTextFor(YType type) {
//Note that proper indentation after each \n" is added automatically
//to align with the parent. The strings created here only need to contain
//indentation spaces to indent *more* than the parent node.
if (type==null) {
//Assume its some kind of pojo bean
return "\n"+YamlIndentUtil.INDENT_STR;
} else if (typeUtil.isMap(type)) {
//ready to enter nested map key on next line
return "\n"+YamlIndentUtil.INDENT_STR;
} if (typeUtil.isSequencable(type)) {
//ready to enter sequence element on next line
return "\n- ";
} else if (typeUtil.isAtomic(type)) {
//ready to enter whatever on the same line
return " ";
} else {
//Assume its some kind of pojo bean
return "\n"+YamlIndentUtil.INDENT_STR;
}
}
private Set<String> getDefinedProperties(SNode contextNode) {
try {
if (contextNode instanceof SChildBearingNode) {
List<SNode> children = ((SChildBearingNode)contextNode).getChildren();
if (CollectionUtil.hasElements(children)) {
Set<String> keys = new HashSet<>(children.size());
for (SNode c : children) {
if (c instanceof SKeyNode) {
keys.add(((SKeyNode) c).getKey());
}
}
return keys;
}
}
} catch (Exception e) {
EditorSupportActivator.log(e);
}
return Collections.emptySet();
}
private List<ICompletionProposal> getValueCompletions(YamlDocument doc, int offset, String query) {
YValueHint[] values = typeUtil.getHintValues(type);
if (values!=null) {
ArrayList<ICompletionProposal> completions = new ArrayList<>();
for (YValueHint value : values) {
double score = FuzzyMatcher.matchScore(query, value.getValue());
if (score!=0 && !value.equals(query)) {
DocumentEdits edits = new DocumentEdits(doc.getDocument());
edits.delete(offset-query.length(), offset);
edits.insert(offset, value.getValue());
completions.add(completionFactory().valueProposal(value.getValue(), query, value.getLabel(), type, score, edits, null));
}
}
return completions;
}
return Collections.emptyList();
}
@Override
public YamlAssistContext traverse(YamlPathSegment s) {
if (s.getType()==YamlPathSegmentType.VAL_AT_KEY) {
if (typeUtil.isSequencable(type) || typeUtil.isMap(type)) {
return contextWith(s, typeUtil.getDomainType(type));
}
String key = s.toPropString();
Map<String, YTypedProperty> subproperties = typeUtil.getPropertiesMap(type);
if (subproperties!=null) {
return contextWith(s, getType(subproperties.get(key)));
}
} else if (s.getType()==YamlPathSegmentType.VAL_AT_INDEX) {
if (typeUtil.isSequencable(type)) {
return contextWith(s, typeUtil.getDomainType(type));
}
}
return null;
}
private YType getType(YTypedProperty prop) {
if (prop!=null) {
return prop.getType();
}
return null;
}
private YamlAssistContext contextWith(YamlPathSegment s, YType nextType) {
if (nextType!=null) {
return new YTypeAssistContext(this, contextPath.append(s), nextType, typeUtil);
}
return null;
}
@Override
public String toString() {
return "TypeContext("+contextPath.toPropString()+"::"+type+")";
}
@Override
public HoverInfo getHoverInfo() {
if (parent!=null) {
return parent.getHoverInfo(contextPath.getLastSegment());
}
return null;
}
public YType getType() {
return type;
}
@Override
public HoverInfo getHoverInfo(YamlPathSegment lastSegment) {
//Hoverinfo is only attached to YTypedProperties so...
switch (lastSegment.getType()) {
case VAL_AT_KEY:
case KEY_AT_KEY:
YTypedProperty prop = getProperty(lastSegment.toPropString());
if (prop!=null) {
return new YPropertyHoverInfo(contextPath.toPropString(), getType(), prop);
}
break;
default:
}
return null;
}
private YTypedProperty getProperty(String name) {
return typeUtil.getPropertiesMap(getType()).get(name);
}
}

View File

@@ -0,0 +1,35 @@
/*******************************************************************************
* Copyright (c) 2016 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.yaml.completions;
import java.util.Collection;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.springframework.ide.eclipse.editor.support.hover.HoverInfo;
import org.springframework.ide.eclipse.editor.support.util.DocumentRegion;
import org.springframework.ide.eclipse.editor.support.yaml.YamlDocument;
import org.springframework.ide.eclipse.editor.support.yaml.path.YamlNavigable;
import org.springframework.ide.eclipse.editor.support.yaml.path.YamlPathSegment;
import org.springframework.ide.eclipse.editor.support.yaml.structure.YamlStructureParser.SNode;
/**
* @author Kris De Volder
*/
public interface YamlAssistContext extends YamlNavigable<YamlAssistContext> {
Collection<ICompletionProposal> getCompletions(YamlDocument doc, SNode node, int offset) throws Exception;
//TODO: conceptually... the right thing would be to only implement the second of these
// two methods and get rid of the first one.
HoverInfo getHoverInfo();
HoverInfo getHoverInfo(YamlPathSegment lastSegment);
HoverInfo getValueHoverInfo(YamlDocument doc, DocumentRegion documentRegion);
}

View File

@@ -0,0 +1,169 @@
/*******************************************************************************
* Copyright (c) 2015 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.yaml.completions;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.text.IRegion;
import org.springframework.ide.eclipse.editor.support.completions.DocumentEdits;
import org.springframework.ide.eclipse.editor.support.util.YamlIndentUtil;
import org.springframework.ide.eclipse.editor.support.yaml.YamlDocument;
import org.springframework.ide.eclipse.editor.support.yaml.path.YamlPath;
import org.springframework.ide.eclipse.editor.support.yaml.path.YamlPathSegment;
import org.springframework.ide.eclipse.editor.support.yaml.path.YamlPathSegment.YamlPathSegmentType;
import org.springframework.ide.eclipse.editor.support.yaml.structure.YamlStructureParser.SChildBearingNode;
import org.springframework.ide.eclipse.editor.support.yaml.structure.YamlStructureParser.SKeyNode;
import org.springframework.ide.eclipse.editor.support.yaml.structure.YamlStructureParser.SNode;
import org.springframework.ide.eclipse.editor.support.yaml.structure.YamlStructureParser.SNodeType;
/**
* Helper class that provides methods for creating the edits in a YamlDocument that
* insert new 'property paths' into the document.
*
* @author Kris De Volder
*/
public class YamlPathEdits extends DocumentEdits {
private YamlDocument doc;
private YamlIndentUtil indentUtil;
public YamlPathEdits(YamlDocument doc) {
super(doc.getDocument());
this.doc = doc;
this.indentUtil = new YamlIndentUtil(doc);
}
/**
* Create the necessary edits to ensure that a given property
* path exists, placing cursor in the right place also to start
* start typing the property value.
* <p>
* This also handles cases where all or some of the path already
* exists. In the former case no edits are performed only cursor
* movement. In the latter case, the right place to start inserting
* the 'missing' portion of the path is found and the edits
* are created there.
*/
public void createPath(SChildBearingNode node, YamlPath path, String appendText) throws Exception {
//This code doesn't handle selection of subddocuments
// or creation of new subdocuments so must not call it on
//ROOT node but start at an appropriate SDocNode (or below)
Assert.isLegal(node.getNodeType()!=SNodeType.ROOT);
if (!path.isEmpty()) {
YamlPathSegment s = path.getSegment(0);
if (s.getType()==YamlPathSegmentType.VAL_AT_KEY) {
String key = s.toPropString();
SKeyNode existing = node.getChildWithKey(key);
if (existing==null) {
createNewPath(node, path, appendText);
} else {
createPath(existing, path.tail(), appendText);
}
}
} else {
//whole path already exists. Just try to move cursor somewhere
// sensible in the existing tail-end-node of the path.
SNode child = node.getFirstRealChild();
if (child!=null) {
moveCursorTo(child.getStart());
} else if (node.getNodeType()==SNodeType.KEY) {
SKeyNode keyNode = (SKeyNode) node;
int colonOffset = keyNode.getColonOffset();
char c = doc.getChar(colonOffset+1);
if (c==' ') {
moveCursorTo(colonOffset+2); //cursor after the ": "
} else {
moveCursorTo(colonOffset+1); //cursor after the ":"
}
}
}
}
private void createNewPath(SChildBearingNode parent, YamlPath path, String appendText) throws Exception {
int indent = getChildIndent(parent);
int insertionPoint = getNewPathInsertionOffset(parent);
boolean startOnNewLine = true;
insert(insertionPoint, createPathInsertionText(path, indent, startOnNewLine, appendText));
}
protected String createPathInsertionText(YamlPath path, int indent, boolean startOnNewLine, String appendText) {
StringBuilder buf = new StringBuilder();
for (int i = 0; i < path.size(); i++) {
if (startOnNewLine||i>0) {
indentUtil.addNewlineWithIndent(indent, buf);
}
String key = path.getSegment(i).toPropString();
buf.append(YamlUtil.stringEscape(key));
buf.append(":");
if (i<path.size()-1) {
indent += YamlIndentUtil.INDENT_BY;
}
}
buf.append(indentUtil.applyIndentation(appendText, indent));
return buf.toString();
}
private int getChildIndent(SNode parent) {
if (parent.getNodeType()==SNodeType.DOC) {
return parent.getIndent();
} else {
return parent.getIndent()+YamlIndentUtil.INDENT_BY;
}
}
private int getNewPathInsertionOffset(SChildBearingNode parent) throws Exception {
int insertAfterLine = doc.getLineOfOffset(parent.getTreeEnd());
while (insertAfterLine>=0 && doc.getLineIndentation(insertAfterLine)==-1) {
insertAfterLine--;
}
if (insertAfterLine<0) {
//This code is probably 'dead' because:
// - it can only occur if all lines in the 'parent' are empty
// - if parent is any other node than SRootNode then it must have at least one
// non-emtpy line
// => parent must be SRootNode and only contain comment or empty lines
// But in that case we will never need to compute a 'new path insertion offset'
// since we will always be in the case where completions are to be inserted
// in place (i.e. at the current cursor).
return 0; //insert at beginning of document
} else {
IRegion r = doc.getLineInformation(insertAfterLine);
return r.getOffset() + r.getLength();
}
}
public void createPathInPlace(SNode contextNode, YamlPath relativePath, int insertionPoint, String appendText) throws Exception {
int indent = getChildIndent(contextNode);
insert(insertionPoint, createPathInsertionText(relativePath, indent, needNewline(contextNode, insertionPoint), appendText));
}
private boolean needNewline(SNode contextNode, int insertionPoint) throws Exception {
if (contextNode.getNodeType()==SNodeType.SEQ) {
// after a '- ' its okay to put key on same line
return false;
} else {
return lineHasTextBefore(insertionPoint);
}
}
private boolean lineHasTextBefore(int insertionPoint) throws Exception {
String textBefore = doc.getLineTextBefore(insertionPoint);
return !textBefore.trim().isEmpty();
}
/**
* Deletes this node, and all of its children.
*/
public void deleteNode(SNode node) throws Exception {
delete(node.getStart(), node.getTreeEnd());
deleteLineBackwardAtOffset(node.getStart());
}
}

View File

@@ -0,0 +1,102 @@
/*******************************************************************************
* Copyright (c) 2016 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.yaml.completions;
/**
* @author Kris De Volder
*/
public class YamlUtil {
/**
* If any one of these is found at the start of a string then the string requires
* to be escaped.
*/
private static final String[] SPECIAL_START = {
//This list is probably not complete.
"!",
"#",
"&",
"*",
">",
"|",
"?",
"{",
"}",
"[",
"]",
",",
"\"",
"'",
"`",
"@",
"- ",
"\t",
" ",
// "\n", //also included in 'special content' so no need to check at start specifically.
// "\r"
};
/**
* If any of these is found at the end of a string then the string requires to be
* escaped.
*/
private static final String[] SPECIAL_END = {
"\t",
" "
// "\n", //also included in 'special content' so no need to check at start specifically.
// "\r"
};
/**
* If any of these is found inside a string then the string requires to be escaped.
*/
private static final String[] SPECIAL_CONTENT = {
": ", " #", "\n", "\r"
};
/**
* Given a string value convert it into a format that can be inserted into a yml file.
*/
public static String stringEscape(String value) {
if (canInsertAsIs(value)) {
return value;
}
//TODO: this does not properly handle values that contain line-breaks (linebreaks are no alowed in single-line stirngs, such as may
// be used for 'keys' in yaml. And although they are allowed in mult-line strings, they will be subject to new end-of-line folding
// this processing of newlines by yaml parser will mean that the value is not the same when parsed.
//For string like that we probably need to resort to double-quoted strings using escape sequences using '\'.
//These cases are rare and not yet implemented here.
return "'"+value.replace("'", "''")+"'";
}
private static boolean canInsertAsIs(String value) {
//See https://www.activestate.com/blog/2014/07/yaml-pro
// section: "Don't Over-quote your Strings"
for (String special : SPECIAL_START) {
if (value.startsWith(special)) {
return false;
}
}
for (String special : SPECIAL_END) {
if (value.endsWith(special)) {
return false;
}
}
for (String special : SPECIAL_CONTENT) {
if (value.contains(special)) {
return false;
}
}
//Nothing special. Safe to include verbatim.
return true;
}
}

View File

@@ -0,0 +1,113 @@
/*******************************************************************************
* Copyright (c) 2015, 2016 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.yaml.hover;
import java.util.List;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Region;
import org.springframework.ide.eclipse.editor.support.hover.HoverInfo;
import org.springframework.ide.eclipse.editor.support.hover.HoverInfoProvider;
import org.springframework.ide.eclipse.editor.support.util.DocumentRegion;
import org.springframework.ide.eclipse.editor.support.yaml.YamlAssistContextProvider;
import org.springframework.ide.eclipse.editor.support.yaml.YamlDocument;
import org.springframework.ide.eclipse.editor.support.yaml.ast.NodeRef;
import org.springframework.ide.eclipse.editor.support.yaml.ast.YamlASTProvider;
import org.springframework.ide.eclipse.editor.support.yaml.ast.YamlFileAST;
import org.springframework.ide.eclipse.editor.support.yaml.completions.YamlAssistContext;
import org.springframework.ide.eclipse.editor.support.yaml.path.YamlPath;
import org.springframework.ide.eclipse.editor.support.yaml.path.YamlPathSegment;
import org.springframework.ide.eclipse.editor.support.yaml.structure.YamlStructureProvider;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.nodes.NodeId;
import org.yaml.snakeyaml.parser.ParserException;
import org.yaml.snakeyaml.scanner.ScannerException;
/**
* Implements {@link HoverInfoProvider} for Yaml files based on {@link YamlAssistContext}.
*
* @author Kris De Volder
*/
public class YamlHoverInfoProvider implements HoverInfoProvider {
private YamlASTProvider astProvider;
private YamlAssistContextProvider assistContextProvider;
private YamlStructureProvider structureProvider;
public YamlHoverInfoProvider (
YamlASTProvider astProvider,
YamlStructureProvider structureProvider,
YamlAssistContextProvider assistContextProvider
) {
Assert.isNotNull(astProvider);
Assert.isNotNull(structureProvider);
Assert.isNotNull(assistContextProvider);
this.astProvider = astProvider;
this.structureProvider = structureProvider;
this.assistContextProvider = assistContextProvider;
}
@Override
public HoverInfo getHoverInfo(IDocument doc, IRegion r) {
YamlFileAST ast = getAst(doc);
if (ast!=null) {
YamlDocument ymlDoc = new YamlDocument(doc, structureProvider);
YamlAssistContext assistContext = assistContextProvider.getGlobalAssistContext(ymlDoc);
if (assistContext!=null) {
List<NodeRef<?>> astPath = ast.findPath(r.getOffset());
final YamlPath path = YamlPath.fromASTPath(astPath);
if (path!=null) {
YamlPath assistPath = path;
if (assistPath.pointsAtKey()) {
//When a path points at a key we must tramsform it to a 'value-terminating path'
// to be able to reuse the 'getHoverInfo' method on YamlAssistContext (as navigation
// into 'key' is not defined for YamlAssistContext.
String key = path.getLastSegment().toPropString();
assistPath = path.dropLast().append(YamlPathSegment.valueAt(key));
}
assistContext = assistPath.traverse(assistContext);
if (assistContext!=null) {
if (path.pointsAtValue()) {
return assistContext.getValueHoverInfo(ymlDoc, new DocumentRegion(doc, r));
}
return assistContext.getHoverInfo();
}
}
}
}
return null;
}
@Override
public IRegion getHoverRegion(IDocument document, int offset) {
YamlFileAST ast = getAst(document);
if (ast!=null) {
Node n = ast.findNode(offset);
if (n!=null && n.getNodeId()==NodeId.scalar) {
int start = n.getStartMark().getIndex();
int end = n.getEndMark().getIndex();
return new Region(start, end-start);
}
}
return null;
}
private YamlFileAST getAst(IDocument doc) {
try {
return astProvider.getAST(doc);
} catch (ParserException|ScannerException e) {
//ignore, the user just typed some crap
}
return null;
}
}

View File

@@ -0,0 +1,30 @@
package org.springframework.ide.eclipse.editor.support.yaml.path;
import java.util.stream.Stream;
import org.springframework.ide.eclipse.editor.support.yaml.ast.YamlFileAST;
import org.yaml.snakeyaml.nodes.Node;
/**
* Pointer to a specific node in Snake yaml parse tree. Supports navigation
* using {@link YamlPath}s, including support for 'ambiguous' steps like
* {@link YamlPathSegment}.anyChild()
* <p>
* Because the 'root node' of a parsed Yaml file is not actually {@link Node}
* in snake yaml. A cursor pointing at the root of a tree is implemented
* differently than a pointer to a node inside the tree. Therefore
* this class is abstract and has two concrete subclasses.
*
* @author Kris De Volder
*/
public abstract class ASTCursor implements YamlNavigable<ASTCursor> {
public abstract Stream<ASTCursor> traverseAmbiguously(YamlPathSegment s);
/**
* Return the node this cursor is pointing to. This is either a {@link Node} or
* a {@link YamlFileAST}.
*/
public abstract Object getNode();
}

View File

@@ -0,0 +1,61 @@
/*******************************************************************************
* Copyright (c) 2017 Spring IDE Developers
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Spring IDE Developers - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.yaml.path;
import java.util.List;
import java.util.stream.Stream;
import org.eclipse.core.runtime.Assert;
import org.springframework.ide.eclipse.editor.support.yaml.ast.YamlFileAST;
import org.yaml.snakeyaml.nodes.Node;
public class ASTRootCursor extends ASTCursor {
private final YamlFileAST currentNode;
public ASTRootCursor(YamlFileAST astRoot) {
Assert.isNotNull(astRoot);
this.currentNode = astRoot;
}
@Override
public YamlFileAST getNode() {
return currentNode;
}
@Override
public Stream<ASTCursor> traverseAmbiguously(YamlPathSegment s) {
switch (s.getType()) {
case KEY_AT_KEY: {
return Stream.empty();
}
case ANY_CHILD: {
return getNode().getNodes().stream().map(NodeCursor::new);
}
case VAL_AT_INDEX: {
List<Node> nodes = getNode().getNodes();
int index = s.toIndex();
int size = nodes.size();
if (index<size && index >= 0) {
return Stream.of(new NodeCursor(nodes.get(index)));
}
return Stream.empty();
}
case VAL_AT_KEY: {
return Stream.empty();
}
default:
Assert.isLegal(false, "Bug? Missing switch case?");
return Stream.empty();
}
}
}

View File

@@ -0,0 +1,44 @@
/*******************************************************************************
* Copyright (c) 2017 Spring IDE Developers
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Spring IDE Developers - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.yaml.path;
import java.util.stream.Stream;
import org.springframework.ide.eclipse.editor.support.util.Streams;
/**
* Abstract superclass for implementing concrete {@link YamlTraversal}s.
* <p>
* Note that allthough this class provides a default implementation for both
* `traverse` and `traverseAmbiguously`, at least one of these methods <b>must<b>
* be overridden by the subclass (otherwise the methods will just call eachother
* in a infinite recursion loop).
* <p>
* To implement a non-ambiguous traversal, override the `traverse` method.
* <p>
* To implement an ambguous traversal, override the `traverseAmbiguously` method
* instead.
*
* @author Kris De Volder
*/
public abstract class AbstractYamlTraversal implements YamlTraversal {
@Override
public <T extends YamlNavigable<T>> Stream<T> traverseAmbiguously(T start) {
return Streams.fromNullable(traverse(start));
}
@Override
public <T extends YamlNavigable<T>> T traverse(T startNode) {
return traverseAmbiguously(startNode).findFirst().orElse(null);
}
}

View File

@@ -0,0 +1,47 @@
/*******************************************************************************
* Copyright (c) 2017 Spring IDE Developers
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Spring IDE Developers - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.yaml.path;
import java.util.stream.Stream;
import org.eclipse.core.runtime.Assert;
public class AlternativeYamlTraversal extends AbstractYamlTraversal {
private YamlTraversal first;
private YamlTraversal second;
public AlternativeYamlTraversal(YamlTraversal first, YamlTraversal second) {
Assert.isLegal(!first.isEmpty());
Assert.isLegal(!second.isEmpty());
this.first = first;
this.second = second;
}
@Override
public <T extends YamlNavigable<T>> Stream<T> traverseAmbiguously(T start) {
return Stream.concat(
first.traverseAmbiguously(start),
second.traverseAmbiguously(start)
);
}
@Override
public boolean canEmpty() {
return first.canEmpty() || second.canEmpty();
}
@Override
public String toString() {
return "Or("+first+", "+second+")";
}
}

View File

@@ -0,0 +1,48 @@
/*******************************************************************************
* Copyright (c) 2017 Spring IDE Developers
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Spring IDE Developers - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.yaml.path;
import java.util.stream.Stream;
/**
* @author Kris De Volder
*/
public class FilteringTraversal extends AbstractYamlTraversal {
private YamlTraversal yamlTraversal;
private YamlTraversal check;
public FilteringTraversal(YamlTraversal yamlTraversal, YamlTraversal check) {
this.yamlTraversal = yamlTraversal;
this.check = check;
}
@Override
public <T extends YamlNavigable<T>> Stream<T> traverseAmbiguously(T start) {
Stream<T> x = yamlTraversal.traverseAmbiguously(start);
return x.filter(target ->
check.traverseAmbiguously(target)
.findAny()
.isPresent()
);
}
@Override
public String toString() {
return "Filter("+yamlTraversal + "has: " + check + ")";
}
@Override
public boolean canEmpty() {
return yamlTraversal.canEmpty();
}
}

View File

@@ -0,0 +1,83 @@
/*******************************************************************************
* Copyright (c) 2020 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.yaml.path;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
public class JavaObjectNav implements YamlNavigable<JavaObjectNav> {
private Object obj;
public JavaObjectNav(Object obj) {
this.obj = obj;
}
public Stream<String> asStringMaybe() {
if (obj instanceof String) {
return Stream.of((String)obj);
}
return Stream.empty();
}
@Override
public Stream<JavaObjectNav> traverseAmbiguously(YamlPathSegment s) {
if (obj==null) {
return Stream.empty();
} else {
switch (s.getType()) {
case ANY_CHILD:
if (obj instanceof List) {
List list = (List) obj;
return list.stream().map(JavaObjectNav::new);
} else if (obj instanceof Map) {
Map<String, ?> map = (Map) obj;
return map.values().stream().map(JavaObjectNav::new);
}
break;
case KEY_AT_KEY:
if (obj instanceof Map) {
Map<String, ?> map = (Map) obj;
String key = s.toPropString();
if (map.containsKey(key)) {
return Stream.of(new JavaObjectNav(key));
}
Object v = map.get(s.toPropString());
if (v!=null) {
return Stream.of(new JavaObjectNav(v));
}
}
break;
case VAL_AT_KEY:
if (obj instanceof Map) {
Map<String, ?> map = (Map) obj;
Object v = map.get(s.toPropString());
if (v!=null) {
return Stream.of(new JavaObjectNav(v));
}
}
break;
case VAL_AT_INDEX:
if (obj instanceof List) {
List list = (List) obj;
int index = s.toIndex();
if (index>=0 && index<list.size()) {
return Stream.of(new JavaObjectNav(list.get(index)));
}
}
break;
}
}
return Stream.empty();
}
}

View File

@@ -0,0 +1,37 @@
/*******************************************************************************
* Copyright (c) 2016 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.yaml.path;
import java.util.Collections;
/**
* Provides a way to compute all the 'aliases' that are considered 'equivalent' to a
* given key. This is used by structure traversals to try alternatives if the exact
* name itself doesn't correspond to a node in the tree.
* <p>
* A default implementation is provided where a key has no 'aliases'.
*
* @author Kris De Volder
*/
public interface KeyAliases {
public static final KeyAliases NONE = new KeyAliases() {
@Override
public Iterable<String> getKeyAliases(String base) {
return Collections.emptyList();
}
public String toString() { return "KeyAliasses.NONE"; };
};
Iterable<String> getKeyAliases(String base);
}

View File

@@ -0,0 +1,95 @@
/*******************************************************************************
* Copyright (c) 2017 Spring IDE Developers
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Spring IDE Developers - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.yaml.path;
import java.util.stream.Stream;
import org.eclipse.core.runtime.Assert;
import org.springframework.ide.eclipse.editor.support.yaml.ast.NodeUtil;
import org.yaml.snakeyaml.nodes.MappingNode;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.nodes.SequenceNode;
/**
* Pointer to a specific {@link Node} in Snake yaml parse tree. Supports navigation
* using {@link YamlPath}s, including support for 'ambiguous' steps like
* {@link YamlPathSegment}.anyChild()
*
* @author Kris De Volder
*/
public class NodeCursor extends ASTCursor {
private final Node currentNode;
public NodeCursor(Node node) {
Assert.isNotNull(node);
this.currentNode = node;
}
@Override
public Stream<ASTCursor> traverseAmbiguously(YamlPathSegment s) {
switch (s.getType()) {
case KEY_AT_KEY: {
String key = s.toPropString();
MappingNode mappingNode = NodeUtil.asMapping(getNode());
if (mappingNode!=null) {
return mappingNode.getValue().stream()
.filter((c) -> key.equals(NodeUtil.asScalar(c.getKeyNode())))
.map((c) -> new NodeCursor(c.getKeyNode()));
}
return Stream.empty();
}
case ANY_CHILD: {
MappingNode mappingNode = NodeUtil.asMapping(getNode());
if (mappingNode!=null) {
return mappingNode.getValue().stream()
.map((c) -> new NodeCursor(c.getValueNode()));
}
SequenceNode sequenceNode = NodeUtil.asSequence(getNode());
if (sequenceNode!=null) {
return sequenceNode.getValue().stream().map(NodeCursor::new);
}
return Stream.empty();
}
case VAL_AT_INDEX: {
SequenceNode seq = NodeUtil.asSequence(getNode());
int index = s.toIndex();
int size = seq.getValue().size();
if (index<size && index >= 0) {
return Stream.of(new NodeCursor(seq.getValue().get(index)));
}
return Stream.empty();
}
case VAL_AT_KEY: {
MappingNode mappingNode = NodeUtil.asMapping(getNode());
if (mappingNode!=null) {
String key = s.toPropString();
return mappingNode.getValue().stream()
.filter((c) -> key.equals(NodeUtil.asScalar(c.getKeyNode())))
.map((c) -> new NodeCursor(c.getValueNode()));
}
return Stream.empty();
}
default:
Assert.isLegal(false, "Bug? Missing switch case?");
return Stream.empty();
}
}
@Override
public Node getNode() {
return currentNode;
}
}

View File

@@ -0,0 +1,47 @@
package org.springframework.ide.eclipse.editor.support.yaml.path;
import java.util.stream.Stream;
import org.eclipse.core.runtime.Assert;
import org.springframework.ide.eclipse.editor.support.util.Streams;
public class RepeatingYamlTraversal extends AbstractYamlTraversal {
private YamlTraversal step;
public RepeatingYamlTraversal(YamlTraversal step) {
Assert.isLegal(!step.canEmpty()); //This implementation is still too simplistic to handle that properly!
// If you hit this assert, then it may be time to make it more sophisticated.
this.step = step;
}
@Override
public <T extends YamlNavigable<T>> Stream<T> traverseAmbiguously(T start) {
if (start==null) {
return Stream.empty();
} else {
return Stream.concat(
Streams.fromNullable(start),
step.traverseAmbiguously(start).flatMap(next -> {
return this.traverseAmbiguously(next);
})
);
}
}
@Override
public String toString() {
return "Repeat("+step+")";
}
@Override
public YamlTraversal repeat() {
//don't make 'Repeat(Repeat(...))'
return this;
}
@Override
public boolean canEmpty() {
return true;
}
}

View File

@@ -0,0 +1,36 @@
/*******************************************************************************
* Copyright (c) 2017 Spring IDE Developers
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Spring IDE Developers - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.yaml.path;
import java.util.stream.Stream;
public class SequencingYamlTraversal extends AbstractYamlTraversal {
private YamlTraversal first;
private YamlTraversal second;
public SequencingYamlTraversal(YamlTraversal first, YamlTraversal second) {
this.first = first;
this.second = second;
}
@Override
public <T extends YamlNavigable<T>> Stream<T> traverseAmbiguously(T start) {
return first.traverseAmbiguously(start)
.flatMap(second::traverseAmbiguously);
}
@Override
public boolean canEmpty() {
return first.canEmpty() && second.canEmpty();
}
}

View File

@@ -0,0 +1,58 @@
/*******************************************************************************
* Copyright (c) 2015 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.yaml.path;
import java.util.Map;
import java.util.stream.Stream;
import org.springframework.ide.eclipse.boot.util.Log;
import org.springframework.ide.eclipse.editor.support.yaml.ast.YamlFileAST;
import org.springframework.ide.eclipse.editor.support.yaml.structure.YamlStructureParser.SNode;
/**
* Different types of things (e.g. {@link ApplicationYamlAssistContext}, {@link SNode} ...) can
* be traversed interpeting {@link YamlPath} as 'navigation operations'. To facilitate
* 'reusable' traversal code, they can implement this interface.
* <p>
* WARNING: both methods in this interface have default implementation. However at least one
* of them must be implemented explicitly otherwise they will call eachother in an infinite recursion!
*/
public interface YamlNavigable<T> {
/**
* Traversal which silently ignores ambiguity by picking the first valid target
* returned by traverseAmbiguously.
*
*/
default T traverse(YamlPathSegment s) throws Exception {
return traverseAmbiguously(s).findFirst().orElse(null);
}
/**
* To support traversal in the face of ambiguous steps (i.e. when a step may lead to multiple valid targets),
* implement this method. For convenience a default implementation is provided which calls `traverse`.
* Obviously, this implementation doesn't truly support ambiguity but it is sufficient for YamlNavigables
* where there is no ambiguity, or if you don't care about it.
*/
default Stream<T> traverseAmbiguously(YamlPathSegment s) {
try {
T it = traverse(s);
return it == null ? Stream.empty() : Stream.of(it);
} catch (Exception e) {
Log.log(e);
return Stream.empty();
}
}
static JavaObjectNav javaObject(Object obj) {
return new JavaObjectNav(obj);
}
}

View File

@@ -0,0 +1,342 @@
/*******************************************************************************
* Copyright (c) 2015, 2016 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.yaml.path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import org.springframework.ide.eclipse.editor.support.yaml.ast.NodeRef;
import org.springframework.ide.eclipse.editor.support.yaml.ast.NodeRef.RootRef;
import org.springframework.ide.eclipse.editor.support.yaml.ast.NodeRef.SeqRef;
import org.springframework.ide.eclipse.editor.support.yaml.ast.NodeRef.TupleValueRef;
import org.springframework.ide.eclipse.editor.support.yaml.ast.NodeUtil;
import org.springframework.ide.eclipse.editor.support.yaml.path.YamlPathSegment.YamlPathSegmentType;
import com.google.common.collect.ImmutableList;
import reactor.core.publisher.Flux;
/**
* @author Kris De Volder
*/
public class YamlPath extends AbstractYamlTraversal {
public static final YamlPath EMPTY = new YamlPath();
private final YamlPathSegment[] segments;
public YamlPath(List<YamlPathSegment> segments) {
this.segments = segments.toArray(new YamlPathSegment[segments.size()]);
}
public YamlPath() {
this.segments = new YamlPathSegment[0];
}
public YamlPath(YamlPathSegment... segments) {
this.segments = segments;
}
public String toPropString() {
StringBuilder buf = new StringBuilder();
boolean first = true;
for (YamlPathSegment s : segments) {
if (first) {
buf.append(s.toPropString());
} else {
buf.append(s.toNavString());
}
first = false;
}
return buf.toString();
}
public String toNavString() {
StringBuilder buf = new StringBuilder();
for (YamlPathSegment s : segments) {
buf.append(s.toNavString());
}
return buf.toString();
}
public YamlPathSegment[] getSegments() {
return segments;
}
/**
* Parse a YamlPath from a dotted property name. The segments are obtained
* by spliting the name at each dot.
*/
public static YamlPath fromProperty(String propName) {
ImmutableList.Builder<YamlPathSegment> segments = ImmutableList.builder();
String delim = ".[]";
StringTokenizer tokens = new StringTokenizer(propName, delim, true);
try {
while (tokens.hasMoreTokens()) {
String token = tokens.nextToken(delim);
if (token.equals(".") || token.equals("]")) {
//Skip it silently
} else if (token.equals("[")) {
String bracketed = tokens.nextToken("]");
if (bracketed.equals("]")) {
//empty string between []? Makes no sense, so ignore that.
} else {
try {
int index = Integer.parseInt(bracketed);
segments.add(YamlPathSegment.valueAt(index));
} catch (NumberFormatException e) {
segments.add(YamlPathSegment.valueAt(bracketed));
}
}
} else {
segments.add(YamlPathSegment.valueAt(token));
}
}
} catch (NoSuchElementException e) {
//Ran out of tokens.
}
return new YamlPath(segments.build());
}
/**
* Create a YamlPath with a single segment (i.e. like 'fromProperty', but does
* not parse '.' as segment separators.
*/
public static YamlPath fromSimpleProperty(String name) {
return new YamlPath(YamlPathSegment.valueAt(name));
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append("YamlPath(");
boolean first = true;
for (YamlPathSegment s : segments) {
if (!first) {
buf.append(", ");
}
buf.append(s);
first = false;
}
buf.append(")");
return buf.toString();
}
public int size() {
return segments.length;
}
public YamlPathSegment getSegment(int segment) {
if (segment>=0 && segment<segments.length) {
return segments[segment];
}
return null;
}
public YamlPath prepend(YamlPathSegment s) {
YamlPathSegment[] newPath = new YamlPathSegment[segments.length+1];
newPath[0] = s;
System.arraycopy(segments, 0, newPath, 1, segments.length);
return new YamlPath(newPath);
}
public YamlPath append(YamlPathSegment s) {
YamlPathSegment[] newPath = Arrays.copyOf(segments, segments.length+1);
newPath[segments.length] = s;
return new YamlPath(newPath);
}
@Override
public <T extends YamlNavigable<T>> Stream<T> traverseAmbiguously(T startNode) {
if (startNode!=null) {
Stream<T> result = Stream.of(startNode);
for (YamlPathSegment s : segments) {
result = result.flatMap((node) -> {
return node.traverseAmbiguously(s);
});
}
return result;
}
return Stream.empty();
}
public YamlPath dropFirst(int dropCount) {
if (dropCount>=size()) {
return EMPTY;
}
if (dropCount==0) {
return this;
}
YamlPathSegment[] newPath = new YamlPathSegment[segments.length-dropCount];
for (int i = 0; i < newPath.length; i++) {
newPath[i] = segments[i+dropCount];
}
return new YamlPath(newPath);
}
public YamlPath dropLast() {
return dropLast(1);
}
public YamlPath dropLast(int dropCount) {
if (dropCount>=size()) {
return EMPTY;
}
if (dropCount==0) {
return this;
}
YamlPathSegment[] newPath = new YamlPathSegment[segments.length-dropCount];
for (int i = 0; i < newPath.length; i++) {
newPath[i] = segments[i];
}
return new YamlPath(newPath);
}
@Override
public boolean isEmpty() {
return segments.length==0;
}
public YamlPath tail() {
return dropFirst(1);
}
/**
* Attempt to convert a path represented as a list of {@link NodeRef} into YamlPath.
* <p>
* Note that not all AST path can be converted into a YamlPath. Some paths in AST
* do not have a corresponding YamlPath. For such cases this method may return null.
*/
public static YamlPath fromASTPath(List<NodeRef<?>> path) {
List<YamlPathSegment> segments = new ArrayList<>(path.size());
for (NodeRef<?> nodeRef : path) {
switch (nodeRef.getKind()) {
case ROOT:
RootRef rref = (RootRef) nodeRef;
segments.add(YamlPathSegment.valueAt(rref.getIndex()));
break;
case KEY: {
String key = NodeUtil.asScalar(nodeRef.get());
if (key==null) {
return null;
} else {
segments.add(YamlPathSegment.keyAt(key));
} }
break;
case VAL: {
TupleValueRef vref = (TupleValueRef) nodeRef;
String key = NodeUtil.asScalar(vref.getTuple().getKeyNode());
if (key==null) {
return null;
} else {
segments.add(YamlPathSegment.valueAt(key));
} }
break;
case SEQ:
SeqRef sref = ((SeqRef)nodeRef);
segments.add(YamlPathSegment.valueAt(sref.getIndex()));
break;
default:
return null;
}
}
return new YamlPath(segments);
}
public YamlPathSegment getLastSegment() {
if (!isEmpty()) {
return segments[segments.length-1];
}
return null;
}
/**
* Attempt to interpret last segment of path as a bean property name.
* @return The name of the property or null if not applicable.
*/
public String getBeanPropertyName() {
if (!isEmpty()) {
YamlPathSegment lastSegment = getLastSegment();
YamlPathSegmentType kind = lastSegment.getType();
if (kind==YamlPathSegmentType.KEY_AT_KEY || kind==YamlPathSegmentType.VAL_AT_KEY) {
return lastSegment.toPropString();
}
}
return null;
}
public boolean pointsAtKey() {
YamlPathSegment s = getLastSegment();
return s!=null && s.getType()==YamlPathSegmentType.KEY_AT_KEY;
}
public boolean pointsAtValue() {
YamlPathSegment s = getLastSegment();
if (s!=null) {
YamlPathSegmentType type = s.getType();
return type==YamlPathSegmentType.VAL_AT_KEY || type==YamlPathSegmentType.VAL_AT_INDEX;
}
return false;
}
public YamlPath commonPrefix(YamlPath other) {
ArrayList<YamlPathSegment> common = new ArrayList<>(this.size());
for (int i = 0; i < this.size(); i++) {
YamlPathSegment s = this.getSegment(i);
if (s.equals(other.getSegment(i))) {
common.add(s);
}
}
return new YamlPath(common);
}
public static YamlPath decode(List<String> encodedSegments) {
return Flux.fromIterable(encodedSegments)
.map(YamlPathSegment::decode)
.collectList()
.map(YamlPath::new)
.block();
}
@Override
public YamlTraversal then(YamlTraversal _other) {
if (isEmpty()) {
return _other;
} else if (_other.isEmpty()) {
return this;
} else if (_other instanceof YamlPathSegment) {
return this.append((YamlPathSegment) _other);
} else if (_other instanceof YamlPath) {
YamlPath other = (YamlPath) _other;
return new YamlPath(
Stream.concat(
Arrays.stream(this.segments),
Arrays.stream(other.segments)
).toArray(sz -> new YamlPathSegment[sz])
);
} else {
return new SequencingYamlTraversal(this, _other);
}
}
@Override
public boolean canEmpty() {
//The empty path is the only one that 'canEmpty' since any step in path moves the cursor.
return isEmpty();
}
}

Some files were not shown because too many files have changed in this diff Show More