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:
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 ("Content"). Unless otherwise indicated below, the
|
||||
Content is provided to you under the terms and conditions of the Eclipse
|
||||
Public License Version 1.0 ("EPL"). 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, "Program" 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
|
||||
("Redistributor") 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>
|
||||
@@ -0,0 +1,6 @@
|
||||
source.. = src/
|
||||
output.. = bin/
|
||||
bin.includes = META-INF/,\
|
||||
.,\
|
||||
plugin.properties,\
|
||||
about.html
|
||||
@@ -0,0 +1,2 @@
|
||||
providerName = Pivotal Inc.
|
||||
pluginName = Editor Support Components
|
||||
@@ -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>
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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) {
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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()+"_";
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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) {
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 + "]";
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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()+")";
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
// }
|
||||
}
|
||||
@@ -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);
|
||||
// }
|
||||
|
||||
}
|
||||
@@ -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+"'");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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>");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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 ...
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<<<<)";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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+")";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user