From f1ac947a0e1543b6d7c414473b0cddc5e42910c9 Mon Sep 17 00:00:00 2001 From: azerr Date: Sat, 17 Sep 2022 01:12:37 +0200 Subject: [PATCH] Provide .editorconfig support (POC) Fixes #87 Signed-off-by: azerr --- org.eclipse.editorconfig/.classpath | 11 + org.eclipse.editorconfig/.gitignore | 2 + org.eclipse.editorconfig/.project | 28 ++ .../.settings/org.eclipse.jdt.core.prefs | 317 +++++++++++++ org.eclipse.editorconfig/META-INF/MANIFEST.MF | 33 ++ org.eclipse.editorconfig/build.properties | 14 + .../icons/editorconfig.png | Bin 0 -> 562 bytes .../icons/full/obj16/property.png | Bin 0 -> 283 bytes .../icons/full/obj16/value.png | Bin 0 -> 336 bytes .../lib/ec4j-core-0.3.0.jar | Bin 0 -> 111556 bytes .../lib/ec4j-ide-support-0.3.0.jar | Bin 0 -> 14884 bytes org.eclipse.editorconfig/plugin.properties | 22 + org.eclipse.editorconfig/plugin.xml | 189 ++++++++ org.eclipse.editorconfig/pom.xml | 11 + .../editorconfig/EditorConfigPlugin.java | 86 ++++ .../editorconfig/IDEEditorConfigManager.java | 185 ++++++++ .../internal/ApplyEditorConfig.java | 58 +++ .../CompositeReconcilingStrategy.java | 179 +++++++ .../internal/EditorConfigImages.java | 84 ++++ .../internal/EditorConfigMessages.java | 46 ++ .../internal/EditorConfigMessages.properties | 28 ++ .../internal/EditorConfigPreferenceStore.java | 333 +++++++++++++ .../EditorConfigPreferenceStoreProvider.java | 15 + .../internal/EditorConfigReconciler.java | 54 +++ .../internal/EditorConfigStartup.java | 34 ++ .../editorconfig/internal/EditorTracker.java | 190 ++++++++ .../codemining/EditorConfigCodeLens.java | 61 +++ .../EditorConfigCodeLensProvider.java | 68 +++ .../internal/codemining/SectionWithLoc.java | 27 ++ .../internal/codemining/SectionsHandler.java | 32 ++ .../EditorConfigCompletionProposal.java | 440 ++++++++++++++++++ .../EditorConfigContentAssistProcessor.java | 67 +++ .../internal/completion/OptionValue.java | 70 +++ .../PositionBasedCompletionProposal.java | 237 ++++++++++ .../folding/EditorConfigFoldingStrategy.java | 176 +++++++ .../internal/hover/EditorConfigTextHover.java | 78 ++++ .../outline/EditorConfigContentProvider.java | 171 +++++++ .../outline/EditorConfigLabelProvider.java | 48 ++ .../outline/EditorConfigOutlinePage.java | 69 +++ .../EditorConfigToOutlineAdapterFactory.java | 24 + .../resource/ContainerResourcePath.java | 65 +++ .../resource/DocumentRandomReader.java | 41 ++ .../internal/resource/FileResource.java | 61 +++ .../ValidateAppliedOptionsStrategy.java | 381 +++++++++++++++ .../ValidateEditorConfigStrategy.java | 185 ++++++++ .../marker/AbstractMarkerResolution.java | 71 +++ .../marker/EditorConfigMarkerResolution.java | 44 ++ .../InsertFinalNewLineMarkerResolution.java | 42 ++ .../validation/marker/MarkerUtils.java | 102 ++++ ...rimTrailingWhitespaceMarkerResolution.java | 41 ++ .../NewEditorConfigFileWizardPage.java | 162 +++++++ .../wizards/NewEditorConfigWizard.java | 192 ++++++++ .../search/AbstractSectionPatternVisitor.java | 30 ++ .../search/CountSectionPatternVisitor.java | 23 + .../search/EditorConfigSearchQuery.java | 102 ++++ .../search/EditorConfigSearchResult.java | 12 + .../editorconfig/utils/EditorUtils.java | 27 ++ .../src/main/resources/.gitkeep | 0 .../src/test/resources/.gitkeep | 0 .../META-INF/MANIFEST.MF | 7 +- org.eclipse.ui.genericeditor/plugin.xml | 1 + .../schema/preferenceStoreProviders.exsd | 158 +++++++ .../IPreferenceStoreProvider.java | 10 + .../ExtensionBasedTextEditor.java | 97 +++- ...ExtensionBasedTextViewerConfiguration.java | 79 +--- .../GenericEditorContentAssistant.java | 2 +- .../genericeditor/GenericEditorPlugin.java | 8 + .../PreferenceStoreProviderRegistry.java | 97 ++++ .../genericeditor/PreferenceStoreWrapper.java | 232 +++++++++ .../compare/CompareViewerCreator.java | 3 +- .../compare/GenericEditorMergeViewer.java | 13 +- .../ui/texteditor/AbstractTextEditor.java | 4 + 72 files changed, 5684 insertions(+), 95 deletions(-) create mode 100644 org.eclipse.editorconfig/.classpath create mode 100644 org.eclipse.editorconfig/.gitignore create mode 100644 org.eclipse.editorconfig/.project create mode 100644 org.eclipse.editorconfig/.settings/org.eclipse.jdt.core.prefs create mode 100644 org.eclipse.editorconfig/META-INF/MANIFEST.MF create mode 100644 org.eclipse.editorconfig/build.properties create mode 100644 org.eclipse.editorconfig/icons/editorconfig.png create mode 100644 org.eclipse.editorconfig/icons/full/obj16/property.png create mode 100644 org.eclipse.editorconfig/icons/full/obj16/value.png create mode 100644 org.eclipse.editorconfig/lib/ec4j-core-0.3.0.jar create mode 100644 org.eclipse.editorconfig/lib/ec4j-ide-support-0.3.0.jar create mode 100644 org.eclipse.editorconfig/plugin.properties create mode 100644 org.eclipse.editorconfig/plugin.xml create mode 100644 org.eclipse.editorconfig/pom.xml create mode 100644 org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/EditorConfigPlugin.java create mode 100644 org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/IDEEditorConfigManager.java create mode 100644 org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/ApplyEditorConfig.java create mode 100644 org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/CompositeReconcilingStrategy.java create mode 100644 org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/EditorConfigImages.java create mode 100644 org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/EditorConfigMessages.java create mode 100644 org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/EditorConfigMessages.properties create mode 100644 org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/EditorConfigPreferenceStore.java create mode 100644 org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/EditorConfigPreferenceStoreProvider.java create mode 100644 org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/EditorConfigReconciler.java create mode 100644 org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/EditorConfigStartup.java create mode 100644 org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/EditorTracker.java create mode 100644 org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/codemining/EditorConfigCodeLens.java create mode 100644 org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/codemining/EditorConfigCodeLensProvider.java create mode 100644 org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/codemining/SectionWithLoc.java create mode 100644 org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/codemining/SectionsHandler.java create mode 100644 org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/completion/EditorConfigCompletionProposal.java create mode 100644 org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/completion/EditorConfigContentAssistProcessor.java create mode 100644 org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/completion/OptionValue.java create mode 100644 org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/completion/PositionBasedCompletionProposal.java create mode 100644 org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/folding/EditorConfigFoldingStrategy.java create mode 100644 org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/hover/EditorConfigTextHover.java create mode 100644 org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/outline/EditorConfigContentProvider.java create mode 100644 org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/outline/EditorConfigLabelProvider.java create mode 100644 org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/outline/EditorConfigOutlinePage.java create mode 100644 org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/outline/EditorConfigToOutlineAdapterFactory.java create mode 100644 org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/resource/ContainerResourcePath.java create mode 100644 org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/resource/DocumentRandomReader.java create mode 100644 org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/resource/FileResource.java create mode 100644 org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/validation/ValidateAppliedOptionsStrategy.java create mode 100644 org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/validation/ValidateEditorConfigStrategy.java create mode 100644 org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/validation/marker/AbstractMarkerResolution.java create mode 100644 org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/validation/marker/EditorConfigMarkerResolution.java create mode 100644 org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/validation/marker/InsertFinalNewLineMarkerResolution.java create mode 100644 org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/validation/marker/MarkerUtils.java create mode 100644 org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/validation/marker/TrimTrailingWhitespaceMarkerResolution.java create mode 100644 org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/wizards/NewEditorConfigFileWizardPage.java create mode 100644 org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/wizards/NewEditorConfigWizard.java create mode 100644 org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/search/AbstractSectionPatternVisitor.java create mode 100644 org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/search/CountSectionPatternVisitor.java create mode 100644 org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/search/EditorConfigSearchQuery.java create mode 100644 org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/search/EditorConfigSearchResult.java create mode 100644 org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/utils/EditorUtils.java create mode 100644 org.eclipse.editorconfig/src/main/resources/.gitkeep create mode 100644 org.eclipse.editorconfig/src/test/resources/.gitkeep create mode 100644 org.eclipse.ui.genericeditor/schema/preferenceStoreProviders.exsd create mode 100644 org.eclipse.ui.genericeditor/src/org/eclipse/ui/genericeditor/IPreferenceStoreProvider.java create mode 100644 org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/PreferenceStoreProviderRegistry.java create mode 100644 org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/PreferenceStoreWrapper.java diff --git a/org.eclipse.editorconfig/.classpath b/org.eclipse.editorconfig/.classpath new file mode 100644 index 000000000..d4842de9b --- /dev/null +++ b/org.eclipse.editorconfig/.classpath @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/org.eclipse.editorconfig/.gitignore b/org.eclipse.editorconfig/.gitignore new file mode 100644 index 000000000..934e0e06f --- /dev/null +++ b/org.eclipse.editorconfig/.gitignore @@ -0,0 +1,2 @@ +/bin +/target diff --git a/org.eclipse.editorconfig/.project b/org.eclipse.editorconfig/.project new file mode 100644 index 000000000..d6056425c --- /dev/null +++ b/org.eclipse.editorconfig/.project @@ -0,0 +1,28 @@ + + + org.eclipse.editorconfig + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.pde.PluginNature + + diff --git a/org.eclipse.editorconfig/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.editorconfig/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 000000000..b3477c854 --- /dev/null +++ b/org.eclipse.editorconfig/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,317 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.source=1.8 +org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines=2147483647 +org.eclipse.jdt.core.formatter.align_type_members_on_columns=false +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_assignment=0 +org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_compact_if=16 +org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80 +org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0 +org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16 +org.eclipse.jdt.core.formatter.alignment_for_expressions_in_for_loop_header=0 +org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0 +org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16 +org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references=0 +org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80 +org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16 +org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_type_arguments=0 +org.eclipse.jdt.core.formatter.alignment_for_type_parameters=0 +org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16 +org.eclipse.jdt.core.formatter.blank_lines_after_imports=1 +org.eclipse.jdt.core.formatter.blank_lines_after_package=1 +org.eclipse.jdt.core.formatter.blank_lines_before_field=0 +org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0 +org.eclipse.jdt.core.formatter.blank_lines_before_imports=1 +org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1 +org.eclipse.jdt.core.formatter.blank_lines_before_method=1 +org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1 +org.eclipse.jdt.core.formatter.blank_lines_before_package=0 +org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1 +org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1 +org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_lambda_body=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false +org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false +org.eclipse.jdt.core.formatter.comment.count_line_length_from_starting_position=true +org.eclipse.jdt.core.formatter.comment.format_block_comments=true +org.eclipse.jdt.core.formatter.comment.format_header=false +org.eclipse.jdt.core.formatter.comment.format_html=true +org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true +org.eclipse.jdt.core.formatter.comment.format_line_comments=true +org.eclipse.jdt.core.formatter.comment.format_source_code=true +org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true +org.eclipse.jdt.core.formatter.comment.indent_root_tags=true +org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert +org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert +org.eclipse.jdt.core.formatter.comment.line_length=80 +org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true +org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true +org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false +org.eclipse.jdt.core.formatter.compact_else_if=true +org.eclipse.jdt.core.formatter.continuation_indentation=2 +org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2 +org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off +org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on +org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false +org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true +org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true +org.eclipse.jdt.core.formatter.indent_empty_lines=false +org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true +org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true +org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true +org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false +org.eclipse.jdt.core.formatter.indentation.size=4 +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_enum_constant=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert +org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert +org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow=insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert +org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert +org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert +org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert +org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert +org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.join_lines_in_comments=true +org.eclipse.jdt.core.formatter.join_wrapped_lines=true +org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false +org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false +org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false +org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false +org.eclipse.jdt.core.formatter.lineSplit=120 +org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false +org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false +org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0 +org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1 +org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_catch_clause=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_enum_constant_declaration=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_for_statment=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_if_while_statement=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_lambda_declaration=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_method_delcaration=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_method_invocation=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_switch_statement=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_try_clause=common_lines +org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true +org.eclipse.jdt.core.formatter.tabulation.char=tab +org.eclipse.jdt.core.formatter.tabulation.size=4 +org.eclipse.jdt.core.formatter.use_on_off_tags=false +org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false +org.eclipse.jdt.core.formatter.wrap_before_assignment_operator=false +org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true +org.eclipse.jdt.core.formatter.wrap_before_conditional_operator=true +org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true +org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true +org.eclipse.jdt.core.javaFormatter=org.eclipse.jdt.core.defaultJavaFormatter diff --git a/org.eclipse.editorconfig/META-INF/MANIFEST.MF b/org.eclipse.editorconfig/META-INF/MANIFEST.MF new file mode 100644 index 000000000..d2cce4a10 --- /dev/null +++ b/org.eclipse.editorconfig/META-INF/MANIFEST.MF @@ -0,0 +1,33 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: %pluginName +Bundle-Vendor: %providerName +Bundle-Localization: plugin +Bundle-SymbolicName: org.eclipse.editorconfig;singleton:=true +Bundle-Version: 0.1.0.qualifier +Require-Bundle: org.eclipse.tm4e.core, + org.eclipse.jface.text, + org.eclipse.core.runtime, + org.eclipse.ui, + org.eclipse.core.resources, + org.eclipse.core.filesystem, + org.eclipse.tm4e.registry, + org.eclipse.ui.ide;resolution:=optional, + org.eclipse.ui.editors, + org.eclipse.tm4e.ui, + org.eclipse.tm4e.languageconfiguration, + org.eclipse.ui.workbench.texteditor, + org.eclipse.search, + org.eclipse.ui.views, + org.eclipse.ui.navigator, + org.eclipse.ui.genericeditor +Bundle-RequiredExecutionEnvironment: JavaSE-1.8 +Bundle-Activator: org.eclipse.editorconfig.EditorConfigPlugin +Bundle-ActivationPolicy: lazy +Export-Package: org.eclipse.editorconfig, + org.eclipse.editorconfig.search, + org.eclipse.editorconfig.utils +Bundle-ClassPath: lib/ec4j-core-0.3.0.jar, + lib/ec4j-ide-support-0.3.0.jar, + . + diff --git a/org.eclipse.editorconfig/build.properties b/org.eclipse.editorconfig/build.properties new file mode 100644 index 000000000..dfae0574a --- /dev/null +++ b/org.eclipse.editorconfig/build.properties @@ -0,0 +1,14 @@ +source.. = src/main/java/,\ + src/main/resources/,\ + src/test/resources +bin.includes = META-INF/,\ + .,\ + plugin.properties,\ + plugin.xml,\ + language-configurations/,\ + syntaxes/,\ + snippets/,\ + icons/,\ + lib/ec4j-core-0.3.0.jar,\ + lib/ec4j-ide-support-0.3.0.jar + diff --git a/org.eclipse.editorconfig/icons/editorconfig.png b/org.eclipse.editorconfig/icons/editorconfig.png new file mode 100644 index 0000000000000000000000000000000000000000..084fd1d5271f866c457c06f2cad4aac4a8a8bd01 GIT binary patch literal 562 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

+Uj?8J*4Esd+$HP;9t}G?}<-| z-r~*wH4OHCEgN|Lk@){J41OuY{|~|Z56jG+@Xzr7>Hdjvy7uPv!LDP{;u zzyet~eyS8=D!39-MA(v?KxW)Pvzs=lZDNh4cHR0)ACz~fEhEgUDN_3W2v&WKS_X3V zz6hN6y=_~}>GpQ9W<0X9U{4z3Y1gTb^WVRpV0ZvdhBd&M32+2{6c5;`Yi7sV5=V8S zB8{sBjvOOK?5f#%N)Nd}+GfBf-|Y$F+h)YybVQ&aHV{)JtuO&uroaLO zk`e=9qa2W-384`Mk+MK66<8sou?H77vQZhIDObtPor62u`EPOez*3t-*H-HsrOrWR zaY!k;t=sXkI3C0HS2+*_9rbYO|KKu_vI|<$-8^_%O$D>cUL=OhBMhqaGm0>6qQ2y& zJ5RU|gUr;OMANP{x&T07Cx<+XF|~7_nIT7#US{TluA_y7)NrC3hWMCo^)tNFytaG- zZ@6^R-6ia1Ek_IJ=yjf(nb5mj7+|D{Avi|kUm9qrdCf|Ogc~QbO}D^yZ0HMwt2}o{ zps+js5)+%(U5UF?8|2pIT|G?TQ#VkNWld}ou?X5TJ&2m!M2Sdh5FZVWGz*ng zl0dA}s%_0>d(H3)OmQksN2Q;c;Fr%!0>q2gjY&F_A|yeT4bnu&P)Ala;(h+P9KgcN z!jG&ugh}@R9M5>RqPSPChQr*5)=--$_;bw({Hv9x z$i?BYCcr>cNZwyx^fVfrHeOKkD)bYWQm76qLRE5<_BS zP@0<%^Dm~HZ^@XO49ib7P#sGXFJ;B^X}eAf+jq%S7g<>JLW^>9VbXitfVIIDK73oO8ZWEFbn;%@2U=|GgmaABX=@-d7>g+G0s3qpX(dja3J%}U@IAo| z*wT)I#enh2z;VH4xR0DCQ~qmAKY>A?GQB&>Ea5q^*$+InJa|`_j}@24(}Qfmx21@3 zO(Rrd1VbmNN{NsN<4{a>!F!`g{6WLdw8vjnM+GF8=E^nSquX|2Ulm2Y@$mGo#8T<| z1{|&zFN?GSD9IPRqE{x>S2Q$VS*V-NZk|^pC8c$`wv$yj>yeIzC_`v*cxBB%0anTB6STx)81nCC_koi3-coQU2TnSR!E#A-dE;j?okxmSOT|A0^yjPVA#G zn5h;k0!C5kdXkj;J}J*r-L=hz7EG#V`6fLz#wvI zwXU3=#PRJQF$a%xK^L%+Je}X!$F@!XesbL@X@Q?TvN#3A$y>Du_=yJU5DY`9yP_o$ zlglN*C!;|2fP0ixr-&JB>x&qQbJ5Q#{zRuK2vqC+uQ~0nMfvaLnzLSHBJYdebpC}o z`aj8DLUwl6c7MZpM9Pm#uk(J%G^a?~Xo1S+_{yi-Lg_G~JKjjO$FXO8f@yaC=2SaXYvQ*%&nii2>%puW z;b#~{L;OryV&ieBs#}vL53UscP6!7kP^)I$s$H*StjZR)m`dfh*@ZPAzH;0p9#Dr> zsJD21XiBJ<2+s~R{gFuYrSNQ--M|jg-5zz^s@!aT zJg%r&xQGI5Lmento<0m&WRDbb#~5MQS1S&90VGG-?~skxoeLyP#f=0RFOrK`8P3C% z%kD*!hft5DQ^JcG#Gdl{skQy~yHEzY!>s2lDUfMG%_TaN<+PbfvP-T_KewPTQKb-) zh^q$c<#xi!-4m{0+HvIl;}}&ym*%dF*J=mLGlz5Rm1T?Ff<{Z2rE{h?E|b zCnrR00xrEc9%qEF`Venr6v__pAec5J5=s`;4}k)j)=LULD7ryLmf>e8x_7?(dV=@lVb-b&1KAw(L%ZNZNHB2YRRn#pwM`P07)@J$is1C4;ntdcmMtyas}{-~jNQ z0%O!fl@!9o!#6NTMI1;5A)!At5y$4Td_RHLVl?P@WqyJgojy0hZ_huiB7UntT%yzrmyDK zs$J6n2}%{`r~?omD;2BMW=ffPsWM@HkF2yztW&P6howUFnGvuNt=6d(9fS}kZVc_i zN2mobb#H}gZi*GtRG?wsg2Ri__*#FgQCs3*IW|aJ+{R(|w~3pkd6Lztv!L|C zhbD=)IKc%GC5qaS`NIjI5o^uLI=esWMV+3~BTeKbDpW70a5CkKhp&VNjO&V{_M(5k zqjtNPb!4hO$yDwdAv)WNR}jIREEp+@3~p*FIrZ0Ea$+VwM(n4>Vk;jLmRmNQk6YY@ zGp;^Qv73@d5=lf#r5%HJ>Wll?4=0yU13^Jpv43bckz9kZK*_J(hkcGz!)l_&1bn!O zeu1=VWHkaY2SKjd4M|n-+^t5V3zj6l68r&wImFemL7~gUPPh>am_QJOr(+Odf&Qwk z#$X{rXIDm*zCQZ&0qLfm9L$0{j?f47j;l2;uC2?J`fKVCt&=<8BVnShNdi2z+=%`bRsy<7XiK zM)tzo$R6P-T*HsdU$c29sAM>(A0rk{ilXI^abvj3$Np zhR18#lBRIAbr@4{EG2Y)1r2v{9mPZNu*FlCp|+CI5P+opev@iOERqrb64x2r`C>!r z5aW9Z+R#&f6)B!GaCS9b>+N|FQo(8dTgHF|wE+d9fT+Af$i7sD5Cds~v=VYY$s|~c z+_Ww6knEuX7%}Ovj(|tSyrO{7eMU11Q$mHSyi2NEBw>omJ1s(x(NDaEJz8DQZz?-{ z4tsbO1N|d=a0fS}Yocal!Ej;)-=8A&;Y6_!b)RYIVd3Ry=0p&0_1bcp?f^Y96#MTb zi4>}F(b-bjgH4_Z{qsTfXzqv~;LhISu5zsKKEy>cDD^XfQ7dI|!$|P<$RzvbOM2lX zhZ#~rjp;z9Tb~W4h0t`uepj@_eVgzNC_kY*z>mbI6eeQVjzZ#Y10b_Ap>MC2NS>)F zJ7qm{Sw>t{=HCYsC$yAK#2eIlIX->i$7QO!VwZj^w~fq3kL&ay&JDlA|4F3$q8*KZ zxTKz0BIbsNBc#ZR)MEq5WGtI4wvaKDpddA!AM5OaV@0k@jsspiWO!;A_JAZgRGyHz zObb!15V(!2N-}zrZdfuO>WYh}Xy%9}hBPo$4MVXB!L^Q(T0 zi>B3yNM@*+ct^eB=7E85(wVBl8@Zl~Ay?nTL7z<5P3u)CBo-kgfJS+<{==)sN#Yw> zq16_8&|+v~7RIhFd&JD*%ffI8PmndWv2;~lAyyXFD1 z`Od~2Grz~q0?4ES+dv@EggB!+6${6Vbygs>^264sRlIfc3sLmtq!qL_YPc&ve!gm& zmJ@xN-5Prdgs)7A9Cu;v9UAg3rQTs(Y%fA!k+rz!Q;o zpY5CY^h?6S8_=e_aMC_1$F2?eowd=Hwz;S=g+MXJ1T>Yr>6UkFVT6qhosXpHx> zZXx0oQR2ahEc*;LtSLf+D1w+rw0vEKqrn7}Gcd-t;;aBtM9^TerzJ;&E*a!jlcSLwmh9nXPmD2;$;^1#K z!dC4ENfxoZ)dXn3TZOx-^2UnHT5^|a{SM+j1)$$%5DlE(uvt?ezEe8!89H(7}BlUeC{L&PPhwL{SpUu}OkX&cm+?c}>5WMdHvH1jCXjWAJjbIYC;k2Jq z`BQ}%NTIYxD_2)le%)6+em08m*6dV>dTQfns^(oL#}7MkuvnN4Bfo9|CbLc|{#diW zX~gW1Mk6#zLwst*kYsonGIsN3f2LwvGHN<_yN-p z@0whJ!lYlg1FS_}I*5B&dL8FPZbm|LBgd)v#I|yFdtq=OdQ8jNI%n(Pvit<#^2{hT znO10e`CF&mfXYGg(L5m#tS}3I-nRlYBB=m}z2!Ho^GQ5_x2$Q@NG?V|8d~X3ux+=s z`R}(X4p_J@K9|aG%lA@yl(|uE@;&{@`oSlmlb*i!u?5uAUOq;+;kb4Kq|x?-k%|?z z*eK+-9$ylO6xdxe0p`W2d5etHud_>Fa?io~*?5zl{8!)kMPdM-L%TeuyQb<_U}-eN zQHCI?7iWx39ns37yF}xLd2?|1bWH{wxF*P_!Hs~(#C0Y#CUr;FkyJlW1y5l9B*|Y> z;qOUu(I@IJ_?0AhU+REl|45SmAi)2na3JKOZ)o%X3?m{HEUn@BV7;0;FgvxW3aPq~ z!w@?GFJAadsg!tdiBTJhiU|i~KsNW_TFTu)URS*jlGjpBU`}LiL{N{vT|f+^Sr{1` z8D|+AIXyiuH~&$$Lhr2$2#6q5_K1zMMKDDxA9-G*+IH3m0+$N%s|d&l;DD4%h8RCz zXtALh_R?@2>d~g1NWxrykX$cM&BTVm^4=H`_Cn^S4QQrXzs;hpC{q$?aM+q?ekdYn z@%jngH#g4DnzwP>q_SWJ*&A0sjCNF(G^z8@21UJ<5((|9r&KJ~L0lWpWe42(lr$cb z3i+|NJ(ivJ-jlB$4oUwhF%;8QgDp;*k!5mwoFvm^gKS-C`6vZUv~Okj_}&%7d=g z;>6#yUe8tT{z~i0=MKq0b$MauJcI9By4D){&X&(7NV;$>xjSu1Y4zhmXU1e^{1ftL z4Cqw1Zjeuz66%o*dg%(bwJjf==O@|4Im5tWm@-y4Zknkd8C#}yGOppAj@>eoMnGFh zmNYG=?rP;;>Nwi#iRUFpp1Hh6`kBj6``ODnrSyPST1@$3Odq5tmXA@mnr5D7R0Wx! zSUyN3P+a)IO9ZW}FF*58!Bt8bh+Qx!>I1H2uRuT)Ec01zE*?|_EgIZD=)4Y|H`-6? zSV>;|)BwqeyZc}SQ34Shb&_p%`jB~3kCA}!768bGa>Y!Y>4)!Ak=uVl^REH;_s~q| zWiEO8dZ?;=g(l@cLi7K6vHZJ+M=EN`%*&#BZ8d7}oEu2O28QASax5^NT@r4=N=YIc z`NX)aF7|(^v(10;X-@*b#BaZSh9!T=Yr9Ji%c`zWFFoyN9OoZ*Y)13wbOW07GFrV1+1_e1n5Hnk-x|sAa)tK(U^|~Q7yjp*poABCf4b+M@6L{0YYOcAfp$~Djiqbmu z5l-K4N*ia_Y)H>#k)U1xiII2gA9RLv*f*gB@Y{)+v|e<5&OYtY-az+@x|r3{ft6d^ z2h+|&>(|=bZ6KX?ZAZ2!Sd#eSU_G0X{?b+W%!!zcjK}B1P!D-;IX+W~!8o;1zERLH|` z&pz~e{fJ4qOSgNPP!j$A8`3E;+s2@GSN4vrkeQ%b8QanEcskQ275{FV%)}071W~FtGbGnW!>tUXc#ye$cpiatualCaO zr&YOXZ=Vu?6#Pjtn3oQIOa7kLV?4sb0(g!$|BWIWmaf>oh1kVNq38uwo#d$np}zf( z#GLq>XhpcEnu*zg54c`-Mk1vo?Q-DskBGh1&$Q=J2Z}HcBoM=ua19 zxuBPS2`v9LZ2lflb`}pm?7nIp&DTJ{_K$%2?V7EkEKo$q5p8 zKIg6G3r)@T#A!YG@$&1J^EU-j6I6O!S_|Icnkc6Oc3QuVAUQz&mP%bs|I*zkpWka$ z%4S}pRp5l#Zdn&JEy|7L(VOGGS1yMsHGAt#KFm7y8%*oRQlHmG$8$jsTlD6>`)Sja zl>qtU(yL$Sr)dtEC}HDda9Q~hmYB$~;-t2ojyi|odYuL4@TP7(BrW_{TJZ`a%3#&s$33${cAJcN;&`8nKy5L?(S#Y*&oP3mYXwlaQWQ^7mRdU&p3@9oE+A zuD2iZn21|vi(ryLcW$BwL3eP|`-yQ02WMm8J0n$sjBIHx1xJfy#j#K17mUP+E>HdS zgrv|b#9_I-UAP)&Ie+aa-{SP0dSRA^wRi^vejV?wrf)My^3<(}kpsg(^K3KZ9+{w1 zvZNX2f^qZ%cvMo0USKpmf?_1-UBlu#qROfu09MR&w=ZI4PI3`eKjWpqC)A%x`PcCM zdtfu?;21=Fon6nqWIF!o*ZZF)E@`c=`=@>LIqTXPO8tY!RjA_Xp}2_j+Y0`6bGrrh~iL-*-BRE)NXJDVl$pi@1KL5njipF%IaPPuS9ok!2x z4WF@3J5}6Z#P4K5VfH=q+`A!pa9eN^wg~1p$tU&aJVeuA%%4MoqRBO3C88rzO3#}N z*qJ*lDPu7isi2}MPPDqfpq-25ixv|Z>CqVScfo)rwJL4IWY)q&CRbi_O(-o>XJhYk zQX|^l7~r~dL)uFfbhB{S_W5{9WskS{YKjq;l3`+RbQjuCJ@bXd`-hR#0iA@QDD>6k zQlBi-v1E!s1c&XT)lcQ-$FS{#?Ctus^#>dj?d{9_h3zI57mX%VnPa8d^3N#XO9h?6 z`8&A2buVwa?W~lvZby*NtNl_n?#;($tb~LDCeixMK}lhGK`-wwcB|thn_Cv%j@ZW) zny|{jO#f3IB*K@F35x49C6?1f6%4I?tn55iP*_(=cfU>my8Z#UpZ*Cn9TT3*u(bjS z|1cxcl;JW%P*-=NCF<@5xc2~doHiE>!k1fp$yvra`&g*}nUdSY#BPH!rQ zbff?a971ojA>vM~`OT)<6gtfxJn=%Wwu;7lAm|A-zbaV3$ZSTd+D477!!0PnmSC)Z zAm3AsA~x(erA-*Ui2COioRK>_^AY7N>)NQKk9(bc>q zmfrCljMdR9nVe6fA_NFgLZ}YVl)b?_%1TbLTE68(hloi@%)fb`5Sk+=#W>K=lrOI`??xth<=1jZUz}G)NW&Ar}?29`j z!7f;HviE3HyOm9l4RrW5WYt7+!rKIa^e@5(-*4kyFXS2daKh7og0 zWps#essLs|sVVhHWknWNpdG?|0tb5x)iW$1(eu?tU%E*hn%=aZ%t3;lE2MlKG8&Ye zMp=eH*0rlx4#%zEO4~boj*kX^2t6I+luOJz*Wa;J#OE>>qxMo2X;^@4styEkfXmvDrb6&zW{C4~MU`$G8 zUOC3}@*{VZV_x#SI!*<#?6 zM0F(vp&rDDoQhq<=2Qd*9#`U77!ofEPF&u8)53uZ8s|!)jfN7+s!bc0V^~$^PKVMe zmvx7lq{ucZ^_@Za+=_W&^1oDTZy^^H9{aGH@Wg)tmDR$#ul%w}%0*cQaI&U(oDI2u zoR2MbBKsmIhtkbz3Wo zQJ-&g^&#Nvs@XSH%_5H%<0YeBrL84a4roTMHYzj!di zPNc-u3aD2EYxZQ+^2|EO_H{yE1aQE9z{<<)vB+baVM5GBGL|b>g*ihgN$35z1Fg>J z$pD*ZbPcosVL4cBycy{+s$1t5(G=p=s11Ny8)LoP*S86IomdqJFqRnnIntfQ0Y9q}i zf=>6UYmNH&HF^NMmI_fGnmj#GGE98NMiQ2Cff@M=M0YLKb%h|wP9GERmj5@+Es)L4 zkEE3bt?G8>XI8~idG?2#mK*gIY*HF6fz z$Agz6cvU-8P~6hZcedeWg9aj$v4XwAw&sX#E5&)>-xw zc#PJa{x|3Ei1BhW8eqh0 z(dsQn(it#@x?ObMJ{w_@;f*5ruYN%-55j zWjstrqOvKYW!*ynz;5gdWp2=2JIQFvHE*|X5X{G66YQH=0H-pGS2n)@31bh|u8ayL zb!ibTNfOa=8Vq=uswTa`p4m|^a&G30f5_L);BK`s*7OB|!rnzaOTxG|F5Nz!g=oK$ zE9m$IY*i=0_{0$mDWQC_vXzU~iqExAN?fh^1x$c8CHz89oaYCKB&>3dzdoAu zZKR!L+;efDrcJ*QToTst`FA|p+kQHN>)l1-$=D?ug+d#gF>|JYPr2_c`qM^Y{vJS* zO3_?+&cZcp;rWD`sRa;#)L<_wDLtV_t)Hi@$OiGEgtqaJ%}gCdO=vPXx?S)@m~HgF z`5FIvoBm^Y!MPTyj@=b#A(vMG5aj^8hL7$>k=wVElW$k`h;m+)7{a5_76=85Xog!Y z&|-3h-B-_nC+9gzQ;3*ytk7EVnIrMtx2Pc5MzhaQttf~>J|YE74X9$SgG?x-vD|y< z3=u;@JAZ|}$`D@i_D7LHhscd8!0)mTvhXu~US$VKyUZ>qwspb4b8;|>U1r5I;QUnK zD}a~q8Lxl2ul+T~{vLf9?w3V9U(xpm@kH={MBo3w9RD3~o{F3QGUF+tq@o) z7noLhke0fIL1FwuDC7k42!@3yw(if93JQXnrDys;KN&6et^Gle@{V);WZP6- zW`z{%Z!OSTviM>YOmWQ=(sDY?f}gYG5)Al4d?Zz*CL)SM8Ghw0MLXI`2O3?o@g}VG%IFO;X)XDqz<^{0tDZ zA`o#d@Hnxd5%64pZrCR+!gjHC8F?4{(&?`wpbV%dZaElNgRmHYp{R>!BW4C(IZFuf zD#)|C><8s3_-N0(99MOA!}kmh_6riRPy_Ai(~p0j@7sNf5MEhzl%dM^q3pTZ=@XWFOxup0AKLigDl^yUY#qH^9S`eb za#P>#@%0-%QTT&F>x56c3a%K{68lDC2|@LWno6kG6UL(SG^m%QJin_J$+72i>3j-H) z0=GAzS-IyZN2lxX^QqL`oZ8}-pVm+l8RE3Ws%W20p^~9y?j6;#u{}5(g3-{CpOQMb zA!T@W#Hb(bipivtn%MCQTpWwCqWrO*Go{*Mz-e}a0VF@fwz+$6^Ea&Q?`>NIpS9aF z?lFQie_r5!{Zs$`A-GZDE?9n5gzzs6B+frRg#Wz26>M~^{piCi zl%Oaf9SbX|@d?j0^-YRH2QJY?OgpWS9N(jDpPE8f`@nOQPF^f6$v-Ad$xwJKG~IUI zbX;6kO5Sv-+vZ^C32Vp2+^Rm__w?#M_RQLy@?PcvtU<62k_+Wy!&I`N1mKCHaG?&! z1@&Xa3qC%i#q5P}k?R!=NFzbYkiVAyvB-+uXUf)%*_R4B#UGzO3tAQvmh*l1ke1le zk*x#2@Dz6F%V9ziw8XQhRHHK}Y5JEd(D9GH{xrFnV7s)^9#~K;=DPe#tGISP%eh!E zQV?)7eaYq@?`>?vGb`DRb7EYL_V}q|>TdMKYOb5&kH!jVnWJf&0KFZRYRpt3$5hP? z4M|OLDva(ZGxRg*So1)6A7~0Vl#)?rOJJ4@J7P1lD56dGGA1Yu1-YrG2Qik06An-G zRj2gCfDA!Q6a-wZm-w#WTK1(kJ`&$`vc4^~$N6Q_agK(bvu#djI`~zh*EKG*ey2KS zt2`M^L37XU`JIGBs?N8;E=hcTBdIN1gV_ZB;lamHqO2l0K06D>W$4_TWiV8ikMIO| zA7H>}9zDLGSm5}UGx4j)j*WcGB4kX!%1&Tk-D^WCxx27Yrf6hL(vGC1s^;FKH%8sb zDeFl6m4?qjP@?eOqpP*NULmI#(jIy%vHwoWk(zyj3`;nu4v3-;2uZga;TSH80VFNB!ilOSXWf>)jZLtfYr_wC_>*G%V7^(O@2O6Lp;IH%95kr4 ziNcLgCd8c?(#k-;C1C{l9H9hAZb3FVR_#Um|4RD`uqvD8ZxoPD>Fy3`q`SKX=|;Md zP`bNQK}u3e8tIUd5J_p221Nl8_-<5uI7jsTU)Q(K#d8ko{hQgD+1c5-cW2#$rwJ!_ zEaxR2ziNGc7fKSnBJ3W`sSO)!bD){R;)4*xx)~ljUhd%6v0tO^6(+AKaj*tk>7hry z9axBkIyYjut2ShS`2lx)C}+p1;gI)+bwK+*MsqQ3{Jkb>$qlD9|CXtMtBg@nc82Lky4BbS_QII6GjlJrmW1d33+tPH!hZ-NVD+ zp>GE{B4M3ylYYz~b6>aS_eaYQO1ijAHmUF&!hL=EG3KeB@=k{D?oqUuKRdnwd4wjp ze_LVMJfn?S{BaF*D_+BhsCy*0&#?BtC*>SU{z9uda|bl9Cv z9Xri0)dPp_v?yFS<{)1=9l~fnGJ-r?GXB;W@^tR~7oX9Q@B&<+m}sChGm|hO?5rr` z_Z3Oh*g8qr_ubT;6_lQ7Rr{VaSq?;)#T~_zT&*q@Un+A6P&c)tWb2O$Lee8`?Kw!l zZFu4salMz^LK{Asit9$ntqx#WJssi}dS0?fLL0DL9m~uYqV9cWJy)rUnqt{eNBj!~WB)*QoudyvCoN9zSmL5L(;z#!U*Fkb0*Vg=1xdKy=hhC zI4bI6SoqCDFJoLex@eKt#BE=J(%GjaSf=+I`VhnpNlk#2rsHtaW-h z!W+v-o_$c4e93NzF_X3^mF+?klXv>1gxFPl4I@piq7!xdTZ128#}NyDki^UBHJJNV zb#tT6ifOLJ&ggx*cR0HuCYx?2c>6_sy897^_#MCPR{7^0177rosml6y@wvO_$(>2T zG;}YiwMCLNXr8?jJz8_s!O^qK-4+NJe9p^n8G@a!zczkHw$1ISFYQdwYMt6x!`{~n zLQLt15v9A0jqpT;G(xKTsL^VC-M2B>fO~(et9=bpzSn9&2VhBN8`In$*w#Am!G=S( zdpPSz&zMd#Q75b6RB#}8SR}~1HdOD=Yq13D&#ON(PX9&b*=&jRczWd9jef_fmkoKz zwH7^+c<^LEb@5aS%J{_K*BbV8Tb&a_2^g+@D za3SH13;VrlrJ(j(!BOK_4ZI~a0FGXf{SkSH8X8-eQvCS86UAl3rJ{RLtDav}7CTwJ zy3i{`s{&7GxjUd1T~9_jO-*`yPl|OeYQ{1Nx3UrSQBsS=lD}Xpu{bvyr5HZFz`(`I z_wJ$b+PkjkClIcCWMY`nA|;CskH0`D-eiZeYMlP)sQ9$9-i1QRX2us?>!anf_nDao z>SxMd*(5gj(|fn1)~1g&+pO-6i$5+%uz=k*`ak-H{BF&SLmSn zmf;{v-En?b^>aGtC@yOIU3J6ZPhvGxOPRh*Ht%1q#VdGfAgjHfPJ5j3!hZ{eWNcRb z)iKO8<|{r%%ErE?>elYTb?Ek?4`P`~(|AKh{%0`dY(wbZ^z@@w>Q}UQsrsz5<-chp zit>giiJ~st3!utEC^>zgvDFsZnaXbIHA(zDz)9yjf~bK@12V0i_)MLryKb$}a=yUv zq%Fm~)8?`9ck3-dOf5-t_LYFWL(<-#$?@2Xgu+NuI_+~9~FB-GvaLCIhchq`vUX?Ej5XJ1rm}}0W$M)+PLl+BRIlSsn zH)k+{u6?ezPge(ZVFOn+4Qc~l6=^%e0Uy=^yx9EfmhOw_6a3g6a1)XeM)#A($QpXx zbtPpnF}(nm1o$NcBaAMKklTU-3;i(hwv7fA)w!GB8`(!)ZpDxO(K8s8q6%u3;$avC6)SBqCJNbIJfY!2?G$PPNF^pph#(D%$xOE zAGr+OQFG;Mc{^MFX(3V9#nM#EV$O!wzSE^T1x}C~>DH>ln8PNY!5c3AXc+vmISc#g zlpULAjs~7=u|Xv(#YPRLyG|8tPhS>Pbsa->I_M3!uYG$PGLl8b7WFwKHa;TBf=^nk zK4@U><(%0H`#Y8=vV1HG6a(=_Pb4UPr!fg~^X+!59rQkofA^xSen6~038S1`9X5|> z_5C?HlcPvOo%y3m7mPatciz5g;gLgJztgGIx-BN(xbv)l3hp)GL!Pr73B-7BgjR#F zlZyK@Gc|e|hwhAO_Q{Lhua?fnUh;WZ$~Q4YpM8dC!;$5Nia(hCu>OHWoVchZBbBwt zAjg|ME1N!?>>0Nb57Y_S*wAQ7V)z)h^#p|w9kU>d#Qrc&^$%p}Rfub#c_9B5k$-4Glx%NG5#`>E(k!`)&+0=6|d!?j$} z(y=!NTo%m*q>!5AZ$zhV2+=ZniROajoVKA#=JQ5zmu!F0Ae)hx1UA1CR`@X9neY6AkHbubC{kci$a*0Wy0@S6n zvr#cM{9R(eMoIbN8W!)d?vl{bmvnkheX0f53aIa>S)kqAhng{ofOF7vRb8L@z6U`h zlYyazDj3w`*_7(RM*PHX7b2-n4ToM1BR`;UX(vnmEk9b&=dK`B?uoosuPQ0HeJlMv zSxuc~%%MQR$xQmAGTYQULsP^b<)7)-v_l)3=_cA&SjV;VD6}BGtLMz~9x`R;ZU2a( zi{HUyQ**cl7w@AzYNbj!^>Q(XTn&we|fs>k2`MwkDcc#&`uI4i5x6|yu zJbD@HtO6f1O(}hNYBNFJ7!~1 zTf)ZL$au!6=m+!Yv7pR=TzE*xHfj8dYzsxamsd+OWT7EjG`j|WiHFwV3(^=~Wc8E` z*xce1l$)tRJ265Pm~;(9d@uWE`&~Pw>hmCR`K4}aKSDH%)5Af)U`iw#3o#+M-RQ|^ z(;xs{(?V<^dKS;lg8Rxcf)iWCg^aaP(#4nIp4dXuH?GBrq96@8e(EoetT=;n;~FyG zBS=i$#qJs93GS!n*4o=*Itx+s^PR zQ2FL3Yu{}ThKRlb2FvhMDh+}PR`{sU3A&t>r6!z&IXT&=`UuPkT&{Tn*(^4dDE}V` zg{wLN)fNup$v0;}Q;UF0=lNq=|C!{zx`mbE@)r$5YZucW8}=>>w5o4kWFLX)KDaFW zu@}7`aY!k#uCE__jWj}WS(%OKJBby^s&VNtvnk4%iGZUSn7tcbuw%qjoclMtXqI)g z;cTQ4WiyC9ck6jSWbrzgn?E`|+rKSDARPsRjzbj05{45@Mml*bnmdVdk5g+=E0Lr| zIKx=J-5$ThiEq*U^_?uIOwq-OX!!iMsCUOo;gW?Xtaa*ilI|6U;`Q9>2(jWqZdjAc zt-7ZxZyFYuaHheyro!vt5^SjJHd{$1vtOj*ct4wfn>ZlvyE;o0a@P`WTsq>SSDtnH zmmqo^y%U+ZH^pm`sugmLCAow>==sg5xdZXjFVk4qjcW_WHL*4N-pz&S3SgymN+!aiISrFP#>KdaoL?M2%WOnNOMCvNQgq)0M-^QKB`!+LOkE$1@rxm z*;*QOX&n3l=c*HR994eefU;nvwmXl^Qcy^g8CX~JA1iuMh-=WY3cCggp8itX%jHc z@)i#87Jo{P+tqCBNxW<_rq)VBv@Amt0r!atEq`9#Q;M*kmvgeB#Mlmf7pwi}3AC#g z3~Jki^P{qRK)6~0cU_swOLzWX#t<}WDb*NJz?a0}H!g2BVD6+6l1YOa9W`O$w56ql zQmqvdA-w@}Cr9SwP1#^CLrL?*L&C%!8Sf2ZHSbMXJ)P%_WU)DXAIOV*Brw#acel#0 z{Tc6%_l^!^rg{$ZJFN|s=r{R%LTtr&Nfu(L(%pjwb!RFPAti2K8j4g-(gA@@dA8GL zWZ-qL7;4*nj3;FDX~SZl>mxjH5fEdGOf~9FH=DZc4P6vHbMrP|+UCBiwhGGTEl;LP z)2-$1_}=&SuA7BLq6&|l&cnQfdmN=IY1jo{N5#jM)+_5fA`cqU7f9kMoAxsFMz$G5 zl?Z3dVzBzeSKiX|X)hiS`* zr(H_*ky>GOeubmT7W25_RhKezLP`L9y9thZ4^L3+iQjbfiTKzZ_mPP~nS3XE)gdOu zEy$n;<`25=ObR^F;wBvwt|1+Eo(ZWR3LcHGtyvNvd=5!r9Gm^dr;)I2h*DC5U)}CQ z7*p|iBhhf;Zp*hXQ|=!`@OL1n$ouRSy~A-m8TB}cIVK9B$US;Iq_Ml4o^!a0e0**I z;a}~=z%lpMyQQk+t*f*%w8H^Q1aRiWV5hgP*Z@wSMyor_&$DjWezgRNPOB>|BEt_x zOY?;f(F-$+&qC*iZIVciz%XX+i;X=A=y?zb(b=*TbxX`})-;A&z*{rK z?jux-4LaG5xnmXEw>0QDtZwc2VYKvaYDul?&FQO&PQnj=+btZobAoaIlMY&Y;vx&t@malXyX z*L_1LA{Ow%id3C6@6CI8D$xdqOr)xrIDxR}MR^4Hhr6Po=UYK<{8;8p*94!OAD@0W zQH0Q$bT(Md@;`B3cp6X{i3x*6G_6&mTv5@jcJH28DZFkBh-av@scE|CF{l4VewI-eoQH`iU^-;drCs-B2l6yH*#5nhy^M z*AMTzh(4zu#zXY5Y`6Z2=*nCF%Sv3CC@(`gCuff#=07=!O_DMYZJd9fg}l zSW%IZOhnJjjOV&;kJBFZJ&*3d9gCDM!sZ=(VbShMs>#1=T)-Si>smxAYAGXVvB#g9 zd$7~@*eZ)Z*iP-i*tdJQ#j55Hk~Q*ap&U-cD7`+KBZw7ywkoB*kid3@GR62Pf9HNS z46A4`mC(3)JD*>KfzK)8mJXfkI#cdILCUk4L5w@m;(qKV46#WpSiK`C_sgFctzp!c z+dpcZE`LIL-hcEuuVqa&^8>~w=rH%xZw%(Ho z-Igk!Lk2Fs&(p@ykXoUS9=O-u4(L!&TjUp++wNo~7+QPhY2s1Nup7U6TInT}gu>$h zMLcR>e%qG7b{v%Bp*`*4d5ih9xgpQP^* zp(ZK9xwHrRA5}9v8QHrx<)d_TBEBh_v*&~-u-hr(C7i^}gVvsWpF1>Impkw_o&v9O zkfg~;eD``dNFulehZKIdL-wQs(Q1KeEty2fb>p zq$H^RzCED+#%zb9!F#bHY0_OUp_bSBm=3>GDdldJPKoO)v>oKiqC~hSnv_Rxa4p!p z6OCOa%Fr-Rb)$){p5V@)mr2SsM!wZBV35N2GNp~E{{d4-=8gWD4Q>^O z&QIy`bD5=PdpytH_V7mGlyTjDO;w)CF|B#ehrTIjfnHjmJiz@n3au%X9O8BvvU)M%Zhl_?Fm_pYGi<@dVQ$%n-X(;3!@6`9>yhKy2Cm^IUQ^srt5SF~*$B{L{4VYV+x`?bFVYCg5( zxe)VqusK$SFp($7y^L|s$TwTvoI4UBxgXnN?BGwlY73h#>@TbcsoX@_S@jxTjnf*J z_GZ}vdTQjojrR%8g?+K8uc;kQamTn#9&+Fdj}4O!N;BixXs0>T=||IQ#7Wxn7~+zx z-MGb=+rAk7@c^SE^Hoj1xQpVn1INCJ`~k^oNTp7;P0`(NqxV!s6iTc{o9*wFW*Fv= zbW>|_W7S!(zJsUOaWKvbzKJsW!m!WlO^K4Xxn^B*J0?7-Ttu|uddx--1mT8+usO#+&HLH+R7EFP& zoZe?@e(QK0N;+|S-ZF%3{0d2%c|ChB;nS(hTNBV;EwP(w!bRSR^%a7GPJU+QPpNHV zR6SHTS|ypBR2`>AIC8j-Z)GiMgnJaWvqHG8^n01p=?67G{B#5}pO!k*YUT4`r2Ol# z#M3M;U`iqeGy-?ZOz9yyk7%jA=*n2 zz!-asdDe^-S)!8iWT~KgSUk@>MqOI~!qc(~qFP090)imV6tb7;%jB&E2F7ZXR+qH9 zY>8#Z_Io^t_{9TM0&uT|ZkV=4YV&ZoU-g|k`?H)2L5s!tYUf<<^ zjp`I-Pgtf%nvqGv$C$V0x)ox`qoE~>k(DUIy>W)rRC~Ko#eG3aja4uYrqRI|9%U=E zJ&;!^|Iss~%jsr1tK zq70&X88EDvSQH&1$=5=9ST;n@_dYx5zWpk4a>}i#Y$0eu97T_%o-<1t?ZGic1jEZB z2#MC@yUoo%<*%i*{HZR-)g_v!3Ljl&N>^+7YaRcPFvCU%lk3Ra;3mgtPt!+);y3#U#XN{-S4GrbJXKYey&Lf1Tk!4Ih%$t{vT zA}P-;taC?fuue#4AKj*-znT!+5=QSCK25Nz3h8#vRCW66j}qDnTq7FO9d@-!^tc!# z%!GNIJPK6&V%rCI&QFhEk1ZbaScZJ3qlKwQnI-XsRo; z4DXZx^iZ%v5fO_14(+Mh4yAJ?s#&buH{74}W2SQEW2-(9Q@*F5C#4Hp zk`brgsi8!E({8bRnGm?^Y`kb0Vca?siNsFG)!Q)6A< zzAZWuo(sBiY=%6+V8o|SFbLDf_q4<%0-f;#>=HRHLk8(R4h6f#1wy`2rf5HVtPd7$ zN*#QRc-$y95%2TVIJe8@r};#V$LwAl(k%P?&Jolv-ZV6U`%pA#+K=iHN$0Y-Ne$_&Qa9P*38lzw6s#ppn|^;M&RzacEHQ8J+B0O zpW{zC8!lCzF@|uGuKPTWMQoR2BaCQCLm+MqWv_Itwe}P(90utH((5{y0-i>C1Fm`m=WV znOv{YU>?ar>gui8hleTsA&-r*zOJtc!a(DC7x*ZMZv<;Q#7;=g3?NWy%B?Hn=Vgx! zeNDxRXf^0Jq+TX`7q^y&i(G5XUD>r(`GUXL2;ZJOev5IT{isPPiOvj#kLymoVqMbf zYWLOk+nCZr<1XrftK}nZ7@H58NGlxbpc43h@oZrMxgwqz;^sR(r`VT_l0o zF{R*?TS$jp%f5Fc)g$1H?~8JDtIHLpn7}f9N@!fZ$Jh0Am>c!@B=%7dU1}43uIlL)<)HdEk{(7GKbjm=lmig=#PDEQITim2g2Xak@?uB&cch4>4 zXI+nGhrZ$+CJok5c!)T>V~wZJH%H*zERkzSbzx635Pxh3+`FvogLq>{YlmkYaWHR6 z1(z%K-l=mgYSN4NS=5~>#_BU}ikuZH)&m>~^1O%ITePYoHTS6pGub&v<>TA#sF87I z5LfPv_>w)%A4l8mjLJ;N)cicHm5Y&Fd9=vz_~U2qnToMzh?ucMm~9WofI|j?Xo)H{ zl+P8Wj2M_!-XDayvK%~-+U%VzPMO%p7l_*(z2T|Tg%C6muY_ZT5YPJRWfx zk9!MaP&`uRO*7|N=q^bF&3I6f%T4pdKJj2ej-qU_L9B>MG2LBJo7YxJ0@ZMKfGm1c zQ-}09VXI`X=iYhvH}M=!lN{J$(HtxEa4M`?)IOu;oYy(77Q~4oWx9opumi4=ZfFb! zL4`yK<$Lt=s7fuOnm!m6$kJso+65-1x5H@~C^l^m%;;-mIE5A$RP<@Gw+1$|qo1-iQ*7Pr#G3l+vNan)xb8R{pZl8AZSkgjgt*3aw#{N^<&=-fqNhOtq>gISil7)+JoP0SbRBk_3E)Lj{ z97IP=F+350vf?*3;|h{~rmEG+g`KncLTx=~KH;VNVxjJ!D*?;rjpBZiC|PH1XE!>Q zfhQYBC34iLwf<3$DYBe9Q(V&wJ24(BcYZw3c-3!U1?Q~z#@c_2h z+}JdajF0ihJY~)Uk+And(cfZ7Z*jy>(M=c=M!L%R>D?H!ft>*MQ{E`E>f}Ha3n_5^ zk`d(o6pehbA2|R|w+AWApw4ol%HmBt2e}4fh>lt3v_6^x;?m3Kbm`qih(;k~OE{d# zM?mCUB1_Oi_$kbC^2Ak35r3-&jZ-EQvx7U5jjJR`O z9NL6NoNd51@()PY9pwIBZ+(AlLe$W4QUxmXxt39rW|J~98sv3hNyUrMVPHI5RC%tZ zqisXuNNXQQG?u^)M-9iu$Bk@v2JNk>_YDHxRKkqliAguEx!0S0=^k@C6g_)2s8`-Y z(rNgj>k*mj?;n&lm(6Q?KJhx+TULZPAm8jxfKh15UvOd0M{`=vP5vH%o$er$)kK;+ z=6ILf)I4wEu5@PncuB6=9QUp`L;=SAm=kg6qSd3b~ z3Xgp$5y)QsY!!#>zl^Yf$&seP*7wm_SkW|Y++kIXzkE>ri9$DawU}=~*18(&GpsP4 z!R?Ba@nQ4uhkhlziS;@8s?`~AZ+Xj-5BR*#qR=*A*&_>Br$d*F?vEK;@!6=w$rrBU zsw+yrNB>rUV6`+FkXCin?_c5ng#+oFxepmV-Oz07J|Tq$(}Txiw_-X*v?C$ZON*9^ z%hG7eYvkS;(8bdsA`ob)XCm!o8oD8TrXBjy!m);GdtYT)i)q_YS)OgWTgjg@W6|Dx z89|IoC*_^9!hIEbg5m~V#_TMK+8)s^+RdBl#R=sxY~#lF37+%V%n_RE4GyPw*r0rg zP^K*?!0Lk=Wzy}(9)tGi_(aX-QEEVw$#R72)RZ7HAt&5gOoc92*;rn#XrO3;M4pF8 zHLAgj8*C7W7p?ScsmV3g>@8bO?NclQP&WtF zRn1^m1J#`FGIsP$zU>vw>@D(mD)H(&1HS_C%u(0OfL*S?uJODSH0A4Vg1Qgjlh>_HzrVgrsGg^^p1_-yVEB zy2ZbuEw9sGm1|-x03DRLr_)BxzR|@*Z}N4Yl)N2&y%6@8vGkR3?=JCe$695xgzD%< z+VvvEoM?LUQJE#e9poLxMc}j^d2M!pr(MVG_b&MF(^u1UQt9>}IUbpy3$kQC8Hv!3 z5Nprp|B6>ATQpOstHMf|E-cNrCE-|_WWxMtxOH?RXJEjVn9+l}j+NIT1pVH(8v7MU zZbI2uPy6ZQkckG^^Z8`C6;*EP_S%JJ7=7m2;TN%Z6ckm6^PWLnjP3(YitDrFsisG= zAHqD!5{5AQ61E6sSM@R^@!#pg_1f7lyOnehZ}oZSoKnN=GAA8#1#b|$9^dFc#TIoB z5p@qxCSxUqq6$Q+YL7x_Z=2Urc(03H*&bON;8z>i^SFA9p#gE7qsijnC8Ptv(*cc< z)dyHHmHju|XBFlltLql6;~tH3hupX=N3h7mn1P;|=;11z4U6L|pR7P{UoAuC4~tky zqLaz1Slakfy`D`+KyE5K=pJ2-ZEYEAe@77h`c4anaJ+AnaF5c{XWs8KFl$eFnhcG_ zCiEHl_~{*^BvkK}mGokYzVbFDdU=N@PI*i93c6sKhu|xwnq2J({p`2R*>ChktFm=FFAm(B_+anSZ=N;6 zX0-StwJorw4MuUm=*Xq%PUBc}Sc<=%sber!-JV5MqZrjsmU@*cCDewKZX{vHrJxn% z6(2d>|9%e|qv^FoS{iR;Et*9fv)db{m9bMaYpw?oEhZvsO^OQv6^g7OP?|%2t!#VW ze=IexP6VJ+M5^MW$Ed(kGamT)a~s#y+d+YqrjxUwt@E#AuaQcFGLS4d8Rh3?Iy##u zALYfAPzLk3y85SO4A=b_n+s;@@=M@M##)l8+x$@$L_?6x8CCjd!?G=h-%s%1Sv}6czxT0@ZSvjO`pv znJ>OE{`h8ZXT#)fW1XpCXg9-&db*?U!1gtpk*rS|p`@3-ye9?aJ(Xn=Dc`45$45u*hdZ5%!E;pp<#LuxUE=5L1P8uwbhnvC!_VfH z=-lVeQ4fOzp@qv~j_epdH!rPEx6*&Z8@g5Ss#PkNA%2fp6+T2tD2+o^DDO@fGXiC`N*^09t(LP z5&ED%?`%__utyK;1*s?Ms8BX;oIMhK7K;JzYe#<(rFp7hZnT89r1Toz!dy7Qz7KI? zOi03fZG?BDG~_8zm(?LlBKwA)Sv1ZCb*>}j$vhiac6R2+)mQkCe8cK1?~Yh^^{uWJ zjQhtSF$9>(m13E=KpmjK+_1&8SdD!R2Y<`EHyAa|WHFrh;NA%gmBy%l+j}n$aVrw` zWAxgc)WgNY5ieFj2Zkq9;euvv2uJJ<&4lvU@%?uPYz_I+eKHuRs%WFi4A@T*A1UM$ zD?WBeHhS5g*bE83%9+BjADhlLfnE`(moS98O0Tf*O2#Plnk3PJ-#6JW<2lkh&eG|$ z-hC6V4kQ-ao#5hZT|bD-RCg?64dJb>G66->;Ef@SW`^Z58Sb+~78rP6cWoR{J{Cp3_O)hjp=V z{W9S_wk|j|YfX=W?(ztw)KwEJFRxKAo`hPb1))HoJpW239fJd~W8sV1WC4dOtv-AYE!f4)?YvuXlPA zZakX5zhbkaVPLJ%KYHl>G{K2NW6u4UQ5zAnh07dRP%v@i}NJ6`N&JXi_k}JQ<;ahoOV*aN5}t%PsagUg0z1RyC}TdMZVRTk1?TKSdF20vy6=RTx> z*_kqHywhUki+|GD9*jCYE4Xz377!h{LNcPP&jwOEltTJX+XQ7QXbhLvkxY&Iz$2Tw zS-{U*Thj|syUxAGsd0WQikNg_le+xg?IKHg)pIGy-nfYYi2(r%3k$&xc(sa~5@E~0 z#PI}3l3@T|?XuvE*cPS) z7WKaou0F;F_88z*e?N9Ae33E*yeY69_JY-)gutJ;O6XMB_SX{^UiY7mAz#vgK!rFJ zPQ3cq4`=-^l9(XRw*B$!uSCVaApR5Z1^I(NAbu_9{?TuA5GJSqrCmtsN56k`z}VUn zL;&Cl0`b#W%;XS;xE`Cm}~ zO{YIWf7&nWFEaw<_@e;^`MXv7LIPIBWNzvFz|_?K>Qfh{q^4HZ=?<)_?SQ#l`1dmq z5Y@iCz*C?i{WD2?O%*`U4XNPddg5fIO#m)o0g@MG1}|H+FBxFopyaN3HK1hZ=w#{$ zP_?taILZLzz<#dl!O7&)8cX^BWHbRX^p`Y%&uZUopr4>*e)mem&V~Z`5w*A=;(R?Y zuL1>EDBvVe0XF!FoL`V$*Eb3czbUklSFp!~{O8eRI*k zVSn>``33Z{pz8_noK({E0X&}qQlX zS`2^&2=I5S_B95|yn>=#cKmvzKr52@Rp5Zc6ktFXbq_CFweLBwa}N~hSEq%IfI{Df z#?F7qFoXBJ46iq74uDkx_yyGeyH)#20Qa7PQuxvH>lw{%)aLI1AOryIQ2qwF5TF$& zILu|2DSi#oU&}Xe&h8KNTND6r)G*)*f6NUId8zP=Q7R5BUz{m^?1)wKu>V!i6dXLE z>XCyC;2SjoCrb{(Zvfw4z%R>7UN3+yR?lrVU>g4dOlc$_fW*S!fWHL*II8Q>0=XIQ zD*)^%1JyPz-t%3yYTu_a;AmG@#Bz539SsBTvc&9tn{j}SW8~EYF`HZ?f*CCbtjQ1#BE({{y3Qgt^yge%;|YxhL{EB zFR|ZLFGh)%GdSH#Bf0R1zhCHH{Nh9*YHjG`bn*4-zqwv|!G(k#7m)!wAVJWndhp@j zDE_AQyFTzA#}2_e_Heu`H4Es_Yv6|zq+=zI!O8rxtsgs2{^E{bGvqIq0M7Fjz&WU| zPxS?dyXuZ9luR9M3~hn&{|{GHEA%B05AdnIfD%B*R!#so_)lZKI>N3O^V{Vw$mW1S z(E?@j&}B@j?Td4(br=(F)Y@U0+`SM=0z2_%U12%9|ewi)#NYldieL` z%!)bt&Iu5)B_IjFL;r5oz7uia=zkxw|E9r>Y+i;7;JW~qXkXmOblIwXOB4TZG=5aI z`!|t}2}}brV2W4;qApO=qtCdWh=!@76A-*yFZI6E&mIZ@&z`{A3Ycbow`$*vXW(#` zn!GY#{ur9qQ(69YR}B)7{u1EhL8nWlB5*2~zWDdtc0J^M77-*`08R%m7f@m6mw-cp z28S1Zl0w?n1Q<4|&K}mLKit^$L@JvaWbXntsR{@jm?wX?YG3w>{{xXrGri&{DLAgD z#4o3ZPytLS;{Y|#Ddk-?IHgNH{OKyM$F516tNQ|AD+1!8`Q3*LH@xx^9Q&%blC-un z`fr;YxDVvV<4f4R7L~dFU#JR(Hl}|^%_kQp{SMe7EFJ^|5eTo&jo_$1)q26@_5GHZ zmOjD-g`}0vtDJtatsG|1ln~2QMi*p4|ZawItwHK|@v6q3glLf%L`?m0f;u zqEN83yZDpqDQtC6jMxF}Hv(Z6=+N{Tzn%hkl<~7j=D+<3QM>tOH=xEaz&JonTy^q# zDwkIMV;o)na@EBDvR=gP5YZw9D8U1cfDA;n{?pe}y6n&O@@X1koMi-5%khUNJeav2 z@7FT!dbkNU`KpWeFgO2T&wlB8xJx}=kGF2zy%+)*$S7bSpxu45d_CT!EU(8Sg|h47 z1eqLgD`4`vA%e?yqqb8us%Q{e(-Kh4Z{0vUw znhD}k8CMPa*GWV0&b)h^ojM3;S_Uvg;Y%{WXSMIz7jR0KJpbvt!O~KH_Y3J0eG7E} zJR@MN)F9-@zJrtd<<6BZvO~b@LuXU>zvPGx{k|>-0Dvoi#6T6oeEJvAt6|~~0*?Ro zG)AoW1N8v>3ShtjAbew;gOj*MA#!$ZrjDY9PC)+bQa68J@tEm(Z(+E2ok`wF`!>i-9AAA2l}ns-)8^oC?SQIr6Z`cEPvH< z05_$X(}O2W0I4@X2nm`~!v}m5=#=$Kw^xDX|F{JLoB#vQv-{fs0U;FdIHL#?oWM^d zT*d#lg6TL9hEDF55b&VW<6T6r6EUH?V_oWf5L zU50(v)p`disHv2`=QTh3d#Ic?LeK zec$VXQv%6P{Y4B{3xk0=rzL%Gyene(w|l)>C<}Ciw;O?jUMlXI3S;1Xzgp1=6c)w= z9QGHlAk``UB7&>Mba+5ST4M?h`+rm3_2zuF&<1E;A=Uz%&ZSiTGv|Lx{A$SsP|?g; zgJWMOn!kk8SC9BHf{2FM4jlGRmO-jFfGhLrfiF;wu{^+`uNvNuh~)1h@#+C4P$3(8 z{ss1GR0VQ&44mbw2QR2Vq_N=zj(sVO|K@?~jpFKlbM=OxFEIcTxb6T4IJZ}~ zIf70n#s1*P|I4he=l$vqKTs780q?j$Bjl?>2HQM&mGSGg2VP3L+Lt5z2KX+Ef310e b&4Nm@&_G%O0>T{lFAtcM;9`J<6~zAo@9=Tg literal 0 HcmV?d00001 diff --git a/org.eclipse.editorconfig/lib/ec4j-ide-support-0.3.0.jar b/org.eclipse.editorconfig/lib/ec4j-ide-support-0.3.0.jar new file mode 100644 index 0000000000000000000000000000000000000000..30834842ad79782e2178f8164f88b86bd1b1e65f GIT binary patch literal 14884 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 AhyVZp literal 0 HcmV?d00001 diff --git a/org.eclipse.editorconfig/plugin.properties b/org.eclipse.editorconfig/plugin.properties new file mode 100644 index 000000000..39e95a7f6 --- /dev/null +++ b/org.eclipse.editorconfig/plugin.properties @@ -0,0 +1,22 @@ +############################################################################### +# Copyright (c) 2015-2017 Angelo Zerr 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 +# http://www.eclipse.org/legal/epl-v10.html +# +# Contributors: +# Angelo Zerr - Initial API and implementation +############################################################################### +pluginName=EditorConfig +providerName=Eclipse EditorConfig + +# Problem +EditorConfigProblem.name=EditorConfig Problem + +# Outline +EditorConfig.navigatorContent=EditorConfig + +# Wizards +NewEditorConfigWizard.name=EditorConfig File +NewEditorConfigWizard.desc=Create an .editorconfig file. \ No newline at end of file diff --git a/org.eclipse.editorconfig/plugin.xml b/org.eclipse.editorconfig/plugin.xml new file mode 100644 index 000000000..3e340c541 --- /dev/null +++ b/org.eclipse.editorconfig/plugin.xml @@ -0,0 +1,189 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + %NewEditorConfigWizard.desc + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.eclipse.editorconfig/pom.xml b/org.eclipse.editorconfig/pom.xml new file mode 100644 index 000000000..f428c4762 --- /dev/null +++ b/org.eclipse.editorconfig/pom.xml @@ -0,0 +1,11 @@ + + 4.0.0 + org.eclipse.editorconfig + eclipse-plugin + + org.eclipse.ec4e + 0.1.0-SNAPSHOT + org.eclipse.ec4e.parent + + diff --git a/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/EditorConfigPlugin.java b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/EditorConfigPlugin.java new file mode 100644 index 000000000..bcf3c3ea3 --- /dev/null +++ b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/EditorConfigPlugin.java @@ -0,0 +1,86 @@ +/** + * Copyright (c) 2017 Angelo ZERR. + * 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 + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Angelo Zerr - initial API and implementation + */ +package org.eclipse.editorconfig; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.editorconfig.internal.EditorConfigImages; +import org.eclipse.jface.resource.ImageRegistry; +import org.eclipse.ui.plugin.AbstractUIPlugin; +import org.osgi.framework.BundleContext; + +/** + * The activator class controls the plug-in life cycle + */ +public class EditorConfigPlugin extends AbstractUIPlugin { + + // The plug-in ID + public static final String PLUGIN_ID = "org.eclipse.editorconfig"; //$NON-NLS-1$ + + // The shared instance + private static EditorConfigPlugin plugin; + + /** + * The constructor + */ + public EditorConfigPlugin() { + } + + @Override + public void start(BundleContext context) throws Exception { + super.start(context); + plugin = this; + IDEEditorConfigManager.getInstance().init(); + } + + @Override + public void stop(BundleContext context) throws Exception { + IDEEditorConfigManager.getInstance().dispose(); + plugin = null; + super.stop(context); + } + + /** + * Returns the shared instance + * + * @return the shared instance + */ + public static EditorConfigPlugin getDefault() { + return plugin; + } + + /** + * Utility method to log errors. + * + * @param thr + * The exception through which we noticed the error + */ + public static void logError(final Throwable thr) { + getDefault().getLog().log(new Status(IStatus.ERROR, PLUGIN_ID, 0, thr.getMessage(), thr)); + } + + /** + * Utility method to log errors. + * + * @param message + * User comprehensible message + * @param thr + * The exception through which we noticed the error + */ + public static void logError(final String message, final Throwable thr) { + getDefault().getLog().log(new Status(IStatus.ERROR, PLUGIN_ID, 0, message, thr)); + } + + @Override + protected void initializeImageRegistry(ImageRegistry registry) { + EditorConfigImages.initalize(registry); + } +} diff --git a/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/IDEEditorConfigManager.java b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/IDEEditorConfigManager.java new file mode 100644 index 000000000..59ed0c5bc --- /dev/null +++ b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/IDEEditorConfigManager.java @@ -0,0 +1,185 @@ +/** + * Copyright (c) 2017 Angelo ZERR. + * 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 + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Angelo Zerr - initial API and implementation + */ +package org.eclipse.editorconfig; + +import java.io.IOException; +import java.util.concurrent.ConcurrentHashMap; + +import org.ec4j.core.Cache; +import org.ec4j.core.EditorConfigConstants; +import org.ec4j.core.EditorConfigLoader; +import org.ec4j.core.PropertyTypeRegistry; +import org.ec4j.core.Resource; +import org.ec4j.core.ResourceProperties; +import org.ec4j.core.ResourcePropertiesService; +import org.ec4j.core.ide.IdeSupportService; +import org.ec4j.core.model.EditorConfig; +import org.ec4j.core.model.Version; +import org.ec4j.core.parser.ErrorHandler; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IResourceChangeEvent; +import org.eclipse.core.resources.IResourceChangeListener; +import org.eclipse.core.resources.IResourceDelta; +import org.eclipse.core.resources.IResourceDeltaVisitor; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.editorconfig.internal.resource.FileResource; + +/** + * IDE editorconfig manager. + * + */ +public class IDEEditorConfigManager { + + /** + * An unchecked wrapper around {@link EditorConfigException} to be able to trow + * properly from lambda expressions. + */ + private static class WrappedEditorConfigException extends RuntimeException { + private static final long serialVersionUID = 1L; + private final IOException cause; + + WrappedEditorConfigException(IOException cause) { + this.cause = cause; + } + } + + /** + * {@link EditorConfig} instance cache. Uses {@link ConcurrentHashMap} + * internally and can thus be accessed from concurrent threads. + */ + private static class EditorConfigCache implements IResourceChangeListener, IResourceDeltaVisitor, Cache { + + private static final long serialVersionUID = 1L; + + private final ConcurrentHashMap entries = new ConcurrentHashMap<>(); + + @Override + public void resourceChanged(IResourceChangeEvent event) { + if (event.getType() == IResourceChangeEvent.POST_CHANGE) { + IResourceDelta delta = event.getDelta(); + if (delta != null) { + try { + delta.accept(this); + } catch (CoreException e) { + EditorConfigPlugin.logError("Error while .editorconfig resource changed", e); + } + } + } + } + + @Override + public boolean visit(IResourceDelta delta) throws CoreException { + IResource resource = delta.getResource(); + if (resource == null) { + return false; + } + switch (resource.getType()) { + case IResource.ROOT: + case IResource.PROJECT: + case IResource.FOLDER: + return true; + case IResource.FILE: + IFile file = (IFile) resource; + if (EditorConfigConstants.EDITORCONFIG.equals(file.getName()) + && delta.getKind() == IResourceDelta.CHANGED) { + entries.remove(new FileResource(file)); + } + } + return false; + } + + @Override + public EditorConfig get(Resource editorConfigFile, EditorConfigLoader loader) throws IOException { + try { + return entries.computeIfAbsent(editorConfigFile, k -> { + try { + return loader.load(k); + } catch (IOException e) { + throw new WrappedEditorConfigException(e); + } + }); + } catch (WrappedEditorConfigException e) { + throw e.cause; + } + } + + public void clear() { + entries.clear(); + } + } + + public static final IDEEditorConfigManager INSTANCE = new IDEEditorConfigManager(); + + private final ResourcePropertiesService resourcePropertiesService; + + private final EditorConfigCache cache; + + private final PropertyTypeRegistry registry; + + private final EditorConfigLoader loader; + + private final Version version; + + private final IdeSupportService ideSupportService; + + public IDEEditorConfigManager() { + this.cache = new EditorConfigCache(); + this.registry = PropertyTypeRegistry.default_(); + this.version = Version.CURRENT; + this.loader = EditorConfigLoader.of(version, registry, ErrorHandler.IGNORING); + this.ideSupportService = new IdeSupportService(registry); + + resourcePropertiesService = ResourcePropertiesService.builder()// + .cache(cache) // + .loader(loader) // + .build(); + } + + public static IDEEditorConfigManager getInstance() { + return INSTANCE; + } + + public void init() { + ResourcesPlugin.getWorkspace().addResourceChangeListener(cache); + } + + public ResourcePropertiesService getResourcePropertiesService() { + return resourcePropertiesService; + } + + public PropertyTypeRegistry getRegistry() { + return registry; + } + + public EditorConfigLoader getLoader() { + return loader; + } + + public Version getVersion() { + return version; + } + + public void dispose() { + ResourcesPlugin.getWorkspace().removeResourceChangeListener(cache); + cache.clear(); + } + + public ResourceProperties queryOptions(IFile file) throws IOException { + return resourcePropertiesService.queryProperties(new FileResource(file)); + } + + public IdeSupportService getIdeSupportService() { + return ideSupportService; + } + +} diff --git a/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/ApplyEditorConfig.java b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/ApplyEditorConfig.java new file mode 100644 index 000000000..9f4661adc --- /dev/null +++ b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/ApplyEditorConfig.java @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2017 Angelo ZERR. + * 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 + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Angelo Zerr - initial API and implementation + */ +package org.eclipse.editorconfig.internal; + +import org.eclipse.core.resources.IFile; +import org.eclipse.jface.text.ITextOperationTarget; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IFileEditorInput; +import org.eclipse.ui.texteditor.AbstractTextEditor; +import org.eclipse.ui.texteditor.ITextEditor; + +public class ApplyEditorConfig { + + private final AbstractTextEditor textEditor; + private final EditorConfigPreferenceStore store; + private final EditorConfigReconciler reconciler; + + /** + * @param store + * @param reconciler + * @throws Exception + */ + public ApplyEditorConfig(AbstractTextEditor textEditor) throws Exception { + this.textEditor = textEditor; + this.store = new EditorConfigPreferenceStore(textEditor); + this.reconciler = new EditorConfigReconciler(store.getEditorStore(), getFile(textEditor)); + } + + public void applyConfig() { + store.applyConfig(); + } + + public void install() { + reconciler.install((ITextViewer) textEditor.getAdapter(ITextOperationTarget.class)); + } + + public void uninstall() { + reconciler.uninstall(); + } + + public static IFile getFile(ITextEditor textEditor) { + IEditorInput input = textEditor.getEditorInput(); + if (input instanceof IFileEditorInput) { + return ((IFileEditorInput) input).getFile(); + } + return null; + } + +} diff --git a/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/CompositeReconcilingStrategy.java b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/CompositeReconcilingStrategy.java new file mode 100644 index 000000000..51b5e0a68 --- /dev/null +++ b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/CompositeReconcilingStrategy.java @@ -0,0 +1,179 @@ +package org.eclipse.editorconfig.internal; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.reconciler.DirtyRegion; +import org.eclipse.jface.text.reconciler.IReconciler; +import org.eclipse.jface.text.reconciler.IReconcilingStrategy; +import org.eclipse.jface.text.reconciler.IReconcilingStrategyExtension; + +/** + * A reconciling strategy consisting of a sequence of internal reconciling + * strategies. By default, all requests are passed on to the contained + * strategies. + * + * @since 3.0 + */ +public class CompositeReconcilingStrategy implements IReconcilingStrategy, IReconcilingStrategyExtension, IReconciler { + + /** The list of internal reconciling strategies. */ + private IReconcilingStrategy[] fStrategies; + + /** + * Creates a new, empty composite reconciling strategy. + */ + public CompositeReconcilingStrategy() { + } + + /** + * Sets the reconciling strategies for this composite strategy. + * + * @param strategies + * the strategies to be set or null + */ + public void setReconcilingStrategies(IReconcilingStrategy[] strategies) { + fStrategies = strategies; + } + + /** + * Returns the previously set stratgies or null. + * + * @return the contained strategies or null + */ + public IReconcilingStrategy[] getReconcilingStrategies() { + return fStrategies; + } + + /* + * @see org.eclipse.jface.text.reconciler.IReconcilingStrategy#setDocument(org. + * eclipse.jface.text.IDocument) + */ + @Override + public void setDocument(IDocument document) { + if (fStrategies == null) + return; + + for (int i = 0; i < fStrategies.length; i++) + fStrategies[i].setDocument(document); + } + + /* + * @see + * org.eclipse.jface.text.reconciler.IReconcilingStrategy#reconcile(org.eclipse. + * jface.text.reconciler.DirtyRegion, org.eclipse.jface.text.IRegion) + */ + @Override + public void reconcile(DirtyRegion dirtyRegion, IRegion subRegion) { + if (fStrategies == null) + return; + + for (int i = 0; i < fStrategies.length; i++) { + try { + fStrategies[i].reconcile(dirtyRegion, subRegion); + } catch (Exception e) { + + } + } + } + + /* + * @see + * org.eclipse.jface.text.reconciler.IReconcilingStrategy#reconcile(org.eclipse. + * jface.text.IRegion) + */ + @Override + public void reconcile(IRegion partition) { + if (fStrategies == null) + return; + + for (int i = 0; i < fStrategies.length; i++) { + try { + fStrategies[i].reconcile(partition); + } catch (Exception e) { + + } + } + } + + /* + * @see org.eclipse.jface.text.reconciler.IReconcilingStrategyExtension# + * setProgressMonitor(org.eclipse.core.runtime.IProgressMonitor) + */ + @Override + public void setProgressMonitor(IProgressMonitor monitor) { + if (fStrategies == null) + return; + + for (int i = 0; i < fStrategies.length; i++) { + if (fStrategies[i] instanceof IReconcilingStrategyExtension) { + IReconcilingStrategyExtension extension = (IReconcilingStrategyExtension) fStrategies[i]; + extension.setProgressMonitor(monitor); + } + } + } + + /* + * @see org.eclipse.jface.text.reconciler.IReconcilingStrategyExtension# + * initialReconcile() + */ + @Override + public void initialReconcile() { + if (fStrategies == null) + return; + + for (int i = 0; i < fStrategies.length; i++) { + if (fStrategies[i] instanceof IReconcilingStrategyExtension) { + IReconcilingStrategyExtension extension = (IReconcilingStrategyExtension) fStrategies[i]; + try { + extension.initialReconcile(); + } catch (Exception e) { + + } + + } + } + } + + @Override + public void install(ITextViewer textViewer) { + if (fStrategies == null) + return; + + for (int i = 0; i < fStrategies.length; i++) { + if (fStrategies[i] instanceof IReconciler) { + IReconciler extension = (IReconciler) fStrategies[i]; + try { + extension.install(textViewer); + } catch (Exception e) { + + } + + } + } + } + + @Override + public void uninstall() { + if (fStrategies == null) + return; + + for (int i = 0; i < fStrategies.length; i++) { + if (fStrategies[i] instanceof IReconciler) { + IReconciler extension = (IReconciler) fStrategies[i]; + try { + extension.uninstall(); + } catch (Exception e) { + + } + + } + } + } + + @Override + public IReconcilingStrategy getReconcilingStrategy(String contentType) { + return null; + } +} diff --git a/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/EditorConfigImages.java b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/EditorConfigImages.java new file mode 100644 index 000000000..7f501be36 --- /dev/null +++ b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/EditorConfigImages.java @@ -0,0 +1,84 @@ +/** + * Copyright (c) 2017 Angelo ZERR. + * 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 + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Angelo Zerr - initial API and implementation + */ +package org.eclipse.editorconfig.internal; + +import java.net.URL; + +import org.eclipse.core.runtime.FileLocator; +import org.eclipse.core.runtime.Path; +import org.eclipse.core.runtime.Platform; +import org.eclipse.editorconfig.EditorConfigPlugin; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.resource.ImageRegistry; +import org.eclipse.swt.graphics.Image; +import org.osgi.framework.Bundle; + +/** + * Images for .editorconfig + * + */ +public class EditorConfigImages { + + private static final String ICONS_PATH = "$nl$/icons/full/"; //$NON-NLS-1$ + private static final String OBJECT = ICONS_PATH + "obj16/"; // basic colors - size 16x16 //$NON-NLS-1$ + + public static final String IMG_PROPERTY = "IMG_PROPERTY"; //$NON-NLS-1$ + public static final String IMG_VALUE = "IMG_VALUE"; //$NON-NLS-1$ + + private EditorConfigImages() { + } + + private static ImageRegistry imageRegistry; + + public static void initalize(ImageRegistry registry) { + imageRegistry = registry; + + declareRegistryImage(IMG_PROPERTY, OBJECT + "property.png"); //$NON-NLS-1$ + declareRegistryImage(IMG_VALUE, OBJECT + "value.png"); //$NON-NLS-1$ + } + + private final static void declareRegistryImage(String key, String path) { + ImageDescriptor desc = ImageDescriptor.getMissingImageDescriptor(); + Bundle bundle = Platform.getBundle(EditorConfigPlugin.PLUGIN_ID); + URL url = null; + if (bundle != null) { + url = FileLocator.find(bundle, new Path(path), null); + if (url != null) { + desc = ImageDescriptor.createFromURL(url); + } + } + imageRegistry.put(key, desc); + } + + /** + * Returns the Image identified by the given key, or + * null if it does not exist. + */ + public static Image getImage(String key) { + return getImageRegistry().get(key); + } + + /** + * Returns the ImageDescriptor identified by the given key, or + * null if it does not exist. + */ + public static ImageDescriptor getImageDescriptor(String key) { + return getImageRegistry().getDescriptor(key); + } + + public static ImageRegistry getImageRegistry() { + if (imageRegistry == null) { + imageRegistry = EditorConfigPlugin.getDefault().getImageRegistry(); + } + return imageRegistry; + } + +} diff --git a/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/EditorConfigMessages.java b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/EditorConfigMessages.java new file mode 100644 index 000000000..3290786ef --- /dev/null +++ b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/EditorConfigMessages.java @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2017 Angelo ZERR. + * 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 + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Angelo Zerr - initial API and implementation + */ +package org.eclipse.editorconfig.internal; + +import org.eclipse.osgi.util.NLS; + +/** + * Helper class to get NLSed messages. + * + */ +public class EditorConfigMessages extends NLS { + + private static final String BUNDLE_NAME = "org.eclipse.editorconfig.internal.EditorConfigMessages"; //$NON-NLS-1$ + + // Buttons + public static String Browse_button; + + // Wizards + public static String NewEditorConfigWizard_windowTitle; + public static String NewEditorConfigFileWizardPage_title; + public static String NewEditorConfigFileWizardPage_description; + public static String NewEditorConfigFileWizardPage_folderText_Label; + public static String NewEditorConfigFileWizardPage_containerSelectionDialog_title; + public static String NewEditorConfigFileWizardPage_folder_required_error; + public static String NewEditorConfigFileWizardPage_folder_noexists_error; + public static String NewEditorConfigFileWizardPage_project_noaccessible_error; + public static String NewEditorConfigFileWizardPage_folder_already_editorconfig_error; + + // Search + public static String EditorConfigSearchQuery_label; + public static String EditorConfigSearchQuery_singularReference; + public static String EditorConfigSearchQuery_pluralReferences; + + static { + NLS.initializeMessages(BUNDLE_NAME, EditorConfigMessages.class); + } + +} diff --git a/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/EditorConfigMessages.properties b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/EditorConfigMessages.properties new file mode 100644 index 000000000..c09eb0c83 --- /dev/null +++ b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/EditorConfigMessages.properties @@ -0,0 +1,28 @@ +############################################################################### +# Copyright (c) 2017 Angelo ZERR. +# 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 +# http://www.eclipse.org/legal/epl-v10.html +# +# Contributors: +# Angelo Zerr - Initial API and implementation +############################################################################### +# Buttons +Browse_button = Browse... + +# Wizard +NewEditorConfigWizard_windowTitle = New EditorConfig +NewEditorConfigFileWizardPage_title = EditorConfig File +NewEditorConfigFileWizardPage_description = Create a new '.editorconfig' file in the specified folder. +NewEditorConfigFileWizardPage_folderText_Label = &Folder: +NewEditorConfigFileWizardPage_containerSelectionDialog_title= Select the parent folder +NewEditorConfigFileWizardPage_folder_required_error = Folder must be specified +NewEditorConfigFileWizardPage_folder_noexists_error = Folder must exist +NewEditorConfigFileWizardPage_project_noaccessible_error = Project must be writable +NewEditorConfigFileWizardPage_folder_already_editorconfig_error = Folder already contains an '.editorconfig' file + +# Search +EditorConfigSearchQuery_label=EditorConfig Section +EditorConfigSearchQuery_singularReference= ''{0}'' - 1 match in {1}ms +EditorConfigSearchQuery_pluralReferences= ''{0}'' - {1} matches in {2}ms \ No newline at end of file diff --git a/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/EditorConfigPreferenceStore.java b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/EditorConfigPreferenceStore.java new file mode 100644 index 000000000..3120f7031 --- /dev/null +++ b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/EditorConfigPreferenceStore.java @@ -0,0 +1,333 @@ +/** + * Copyright (c) 2017 Angelo ZERR. + * 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 + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Angelo Zerr - initial API and implementation + */ +package org.eclipse.editorconfig.internal; + +import java.io.IOException; +import java.util.Map; + +import org.ec4j.core.ResourceProperties; +import org.ec4j.core.model.Property; +import org.ec4j.core.model.PropertyType; +import org.ec4j.core.model.PropertyType.EndOfLineValue; +import org.ec4j.core.model.PropertyType.IndentStyleValue; +import org.eclipse.core.resources.IFile; +import org.eclipse.editorconfig.IDEEditorConfigManager; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IDocumentExtension4; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.editors.text.IEncodingSupport; +import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants; +import org.eclipse.ui.texteditor.ITextEditor; + +public class EditorConfigPreferenceStore implements IPreferenceStore { + + public static final String EDITOR_SPACES_FOR_TABS = AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SPACES_FOR_TABS; + public static final String EDITOR_TAB_WIDTH = AbstractDecoratedTextEditorPreferenceConstants.EDITOR_TAB_WIDTH; + public static final String EDITOR_TRIM_TRAILING_WHITESPACE = "trim_trailing_whitespace"; + public static final String EDITOR_INSERT_FINAL_NEWLINE = "insert_final_newline"; + + private final ITextEditor textEditor; + private IPreferenceStore editorStore; + + private Boolean spacesForTabs; + private Integer tabWidth; + private String endOfLine; + private boolean trimTrailingWhitespace; + private boolean insertFinalNewline; + + public EditorConfigPreferenceStore(ITextEditor textEditor) { + this.textEditor = textEditor; + this.editorStore = textEditor.getAdapter(IPreferenceStore.class); //PreferenceStoreHelper.contributeToPreferenceStore(textEditor, this); + } + + public void applyConfig() { + IFile file = ApplyEditorConfig.getFile(textEditor); + if (file != null) { + try { + Boolean oldSpacesForTabs = spacesForTabs; + spacesForTabs = null; + Integer oldTabWidth = tabWidth; + tabWidth = null; + ResourceProperties result = IDEEditorConfigManager.getInstance().queryOptions(file); + + final IndentStyleValue indetStyle = result.getValue(PropertyType.indent_style.getName(), null, false); + if (indetStyle != null) { + spacesForTabs = indetStyle == IndentStyleValue.space; + if (oldSpacesForTabs != spacesForTabs) { + editorStore.firePropertyChangeEvent(EDITOR_SPACES_FOR_TABS, oldSpacesForTabs, + spacesForTabs); + } + } + + final Integer indetSize = result.getValue(PropertyType.indent_size.getName(), null, false); + if (indetSize != null) { + tabWidth = indetSize.intValue(); + if (oldTabWidth != tabWidth) { + editorStore.firePropertyChangeEvent(EDITOR_TAB_WIDTH, oldTabWidth, tabWidth); + } + } + + final EndOfLineValue eol = result.getValue(PropertyType.end_of_line.getName(), null, false); + if (eol != null) { + IEditorInput editorInput = textEditor.getEditorInput(); + IDocument document = textEditor.getDocumentProvider().getDocument(editorInput); + if (document instanceof IDocumentExtension4) { + ((IDocumentExtension4) document) + .setInitialLineDelimiter(eol.getEndOfLineString()); + } + } + + final String charset = result.getValue(PropertyType.charset.getName(), null, false); + if (charset != null) { + IEncodingSupport encodingSupport = textEditor.getAdapter(IEncodingSupport.class); + if (encodingSupport != null) { + encodingSupport.setEncoding(charset.trim().toUpperCase()); + } + } + + final Boolean trimTrailigWs = result.getValue(PropertyType.trim_trailing_whitespace.getName(), null, false); + if (trimTrailigWs != null) { + boolean oldTrimTrailingWhitespace = trimTrailingWhitespace; + trimTrailingWhitespace = trimTrailigWs.booleanValue(); + if (oldTrimTrailingWhitespace != trimTrailingWhitespace) { + editorStore.firePropertyChangeEvent(EDITOR_TRIM_TRAILING_WHITESPACE, + oldTrimTrailingWhitespace, trimTrailingWhitespace); + } + } + + final Boolean insertFinalNl = result.getValue(PropertyType.insert_final_newline.getName(), null, false); + if (insertFinalNl != null) { + boolean oldInsertFinalNewline = insertFinalNewline; + insertFinalNewline = insertFinalNl.booleanValue(); + if (oldInsertFinalNewline != insertFinalNewline) { + editorStore.firePropertyChangeEvent(EDITOR_INSERT_FINAL_NEWLINE, oldInsertFinalNewline, + insertFinalNewline); + } + } + } catch (IOException e) { + e.printStackTrace(); + } + + } + } + + public IPreferenceStore getEditorStore() { + return editorStore; + } + + @Override + public void addPropertyChangeListener(IPropertyChangeListener listener) { + + } + + @Override + public boolean contains(String name) { + if (EDITOR_SPACES_FOR_TABS.equals(name)) { + return spacesForTabs != null; + } else if (EDITOR_TAB_WIDTH.equals(name)) { + return tabWidth != null; + } else if (EDITOR_TRIM_TRAILING_WHITESPACE.equals(name)) { + return true; + } else if (EDITOR_INSERT_FINAL_NEWLINE.equals(name)) { + return true; + } + return false; + } + + @Override + public boolean getBoolean(String name) { + if (EDITOR_SPACES_FOR_TABS.equals(name)) { + return spacesForTabs; + } else if (EDITOR_TRIM_TRAILING_WHITESPACE.equals(name)) { + return trimTrailingWhitespace; + } else if (EDITOR_INSERT_FINAL_NEWLINE.equals(name)) { + return insertFinalNewline; + } + return false; + } + + @Override + public int getInt(String name) { + if (EDITOR_TAB_WIDTH.equals(name)) { + return tabWidth; + } + return 0; + } + + @Override + public void firePropertyChangeEvent(String name, Object oldValue, Object newValue) { + + } + + @Override + public boolean getDefaultBoolean(String name) { + return false; + } + + @Override + public double getDefaultDouble(String name) { + // TODO Auto-generated method stub + return 0; + } + + @Override + public float getDefaultFloat(String name) { + // TODO Auto-generated method stub + return 0; + } + + @Override + public int getDefaultInt(String name) { + // TODO Auto-generated method stub + return 0; + } + + @Override + public long getDefaultLong(String name) { + // TODO Auto-generated method stub + return 0; + } + + @Override + public String getDefaultString(String name) { + // TODO Auto-generated method stub + return null; + } + + @Override + public double getDouble(String name) { + // TODO Auto-generated method stub + return 0; + } + + @Override + public float getFloat(String name) { + // TODO Auto-generated method stub + return 0; + } + + @Override + public long getLong(String name) { + // TODO Auto-generated method stub + return 0; + } + + @Override + public String getString(String name) { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean isDefault(String name) { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean needsSaving() { + // TODO Auto-generated method stub + return false; + } + + @Override + public void putValue(String name, String value) { + // TODO Auto-generated method stub + + } + + @Override + public void removePropertyChangeListener(IPropertyChangeListener listener) { + // TODO Auto-generated method stub + + } + + @Override + public void setDefault(String name, double value) { + // TODO Auto-generated method stub + + } + + @Override + public void setDefault(String name, float value) { + // TODO Auto-generated method stub + + } + + @Override + public void setDefault(String name, int value) { + // TODO Auto-generated method stub + + } + + @Override + public void setDefault(String name, long value) { + // TODO Auto-generated method stub + + } + + @Override + public void setDefault(String name, String defaultObject) { + // TODO Auto-generated method stub + + } + + @Override + public void setDefault(String name, boolean value) { + // TODO Auto-generated method stub + + } + + @Override + public void setToDefault(String name) { + // TODO Auto-generated method stub + + } + + @Override + public void setValue(String name, double value) { + // TODO Auto-generated method stub + + } + + @Override + public void setValue(String name, float value) { + // TODO Auto-generated method stub + + } + + @Override + public void setValue(String name, int value) { + // TODO Auto-generated method stub + + } + + @Override + public void setValue(String name, long value) { + // TODO Auto-generated method stub + + } + + @Override + public void setValue(String name, String value) { + // TODO Auto-generated method stub + + } + + @Override + public void setValue(String name, boolean value) { + // TODO Auto-generated method stub + + } + +} diff --git a/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/EditorConfigPreferenceStoreProvider.java b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/EditorConfigPreferenceStoreProvider.java new file mode 100644 index 000000000..fb29c4ffd --- /dev/null +++ b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/EditorConfigPreferenceStoreProvider.java @@ -0,0 +1,15 @@ +package org.eclipse.editorconfig.internal; + +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.text.source.ISourceViewer; +import org.eclipse.ui.genericeditor.IPreferenceStoreProvider; +import org.eclipse.ui.texteditor.ITextEditor; + +public class EditorConfigPreferenceStoreProvider implements IPreferenceStoreProvider { + + @Override + public IPreferenceStore getPreferenceStore(ISourceViewer viewer, ITextEditor editor) { + return new EditorConfigPreferenceStore(editor); + } + +} diff --git a/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/EditorConfigReconciler.java b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/EditorConfigReconciler.java new file mode 100644 index 000000000..cf086c774 --- /dev/null +++ b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/EditorConfigReconciler.java @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2017 Angelo ZERR. + * 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 + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Angelo Zerr - initial API and implementation + */ +package org.eclipse.editorconfig.internal; + +import org.ec4j.core.EditorConfigConstants; +import org.eclipse.core.resources.IResource; +import org.eclipse.editorconfig.internal.folding.EditorConfigFoldingStrategy; +import org.eclipse.editorconfig.internal.validation.ValidateAppliedOptionsStrategy; +import org.eclipse.editorconfig.internal.validation.ValidateEditorConfigStrategy; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.reconciler.IReconciler; +import org.eclipse.jface.text.reconciler.IReconcilingStrategy; +import org.eclipse.jface.text.reconciler.MonoReconciler; + +public class EditorConfigReconciler extends MonoReconciler { + + public EditorConfigReconciler(IPreferenceStore preferenceStore, IResource resource) { + super(create(preferenceStore, resource), true); + } + + private static IReconcilingStrategy create(IPreferenceStore preferenceStore, IResource resource) { + if (EditorConfigConstants.EDITORCONFIG.equals(resource.getName())) { + // it's an .editorconfig file, add validation + CompositeReconcilingStrategy strategy = new CompositeReconcilingStrategy(); + strategy.setReconcilingStrategies(new IReconcilingStrategy[] { new ValidateEditorConfigStrategy(resource), + new ValidateAppliedOptionsStrategy(preferenceStore, resource), new EditorConfigFoldingStrategy() }); + return strategy; + } + return new ValidateAppliedOptionsStrategy(preferenceStore, resource); + } + + @Override + public void install(ITextViewer textViewer) { + super.install(textViewer); + ((IReconciler) getReconcilingStrategy(IDocument.DEFAULT_CONTENT_TYPE)).install(textViewer); + } + + @Override + public void uninstall() { + super.uninstall(); + ((IReconciler) getReconcilingStrategy(IDocument.DEFAULT_CONTENT_TYPE)).uninstall(); + } + +} diff --git a/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/EditorConfigStartup.java b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/EditorConfigStartup.java new file mode 100644 index 000000000..ee96f4529 --- /dev/null +++ b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/EditorConfigStartup.java @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2017 Angelo ZERR. + * 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 + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Angelo Zerr - initial API and implementation + */ +package org.eclipse.editorconfig.internal; + +import org.eclipse.ui.IStartup; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.PlatformUI; + +/** + * Initialize {@link EditorTracker} when eclipse startup. + * + */ +public class EditorConfigStartup implements IStartup { + + @Override + public void earlyStartup() { + final IWorkbench workbench = PlatformUI.getWorkbench(); + workbench.getDisplay().asyncExec(() -> { + IWorkbenchWindow window = workbench.getActiveWorkbenchWindow(); + if (window != null) { + EditorTracker.getInstance(); + } + }); + } +} diff --git a/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/EditorTracker.java b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/EditorTracker.java new file mode 100644 index 000000000..20b51877f --- /dev/null +++ b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/EditorTracker.java @@ -0,0 +1,190 @@ +/** + * Copyright (c) 2017 Angelo ZERR. + * 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 + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Angelo Zerr - initial API and implementation + */ +package org.eclipse.editorconfig.internal; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.editorconfig.EditorConfigPlugin; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.IEditorReference; +import org.eclipse.ui.IPageListener; +import org.eclipse.ui.IPartListener; +import org.eclipse.ui.IWindowListener; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.IWorkbenchPart; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.texteditor.AbstractTextEditor; + +/** + * Editor tracker used to + * + *

    + *
  • update {@link IPreferenceStore} of the opened text editor by adding the + * {@link EditorConfigPreferenceStore}.
  • + *
  • call {@link EditorConfigPreferenceStore#applyConfig()} when editor has + * focus to apply properties coming from .editorconfig files.
  • + *
+ * + */ +public class EditorTracker implements IWindowListener, IPageListener, IPartListener { + + private static EditorTracker INSTANCE; + + private Map applies = new HashMap<>(); + + private EditorTracker() { + init(); + } + + public static EditorTracker getInstance() { + if (INSTANCE == null) { + INSTANCE = new EditorTracker(); + } + return INSTANCE; + } + + private void init() { + if (PlatformUI.isWorkbenchRunning()) { + IWorkbench workbench = EditorConfigPlugin.getDefault().getWorkbench(); + if (workbench != null) { + IWorkbenchWindow[] windows = workbench.getWorkbenchWindows(); + for (IWorkbenchWindow window : windows) { + windowOpened(window); + } + EditorConfigPlugin.getDefault().getWorkbench().addWindowListener(this); + } + } + } + + @Override + public void windowActivated(IWorkbenchWindow window) { + } + + @Override + public void windowDeactivated(IWorkbenchWindow window) { + } + + @Override + public void windowClosed(IWorkbenchWindow window) { + IWorkbenchPage[] pages = window.getPages(); + for (IWorkbenchPage page : pages) { + pageClosed(page); + } + window.removePageListener(this); + } + + @Override + public void windowOpened(IWorkbenchWindow window) { + if (window.getShell() != null) { + IWorkbenchPage[] pages = window.getPages(); + for (IWorkbenchPage page : pages) { + pageOpened(page); + } + window.addPageListener(this); + } + } + + @Override + public void pageActivated(IWorkbenchPage page) { + } + + @Override + public void pageClosed(IWorkbenchPage page) { + IEditorReference[] rs = page.getEditorReferences(); + for (IEditorReference r : rs) { + IEditorPart part = r.getEditor(false); + if (part != null) { + editorClosed(part); + } + } + page.removePartListener(this); + } + + @Override + public void pageOpened(IWorkbenchPage page) { + IEditorReference[] rs = page.getEditorReferences(); + for (IEditorReference r : rs) { + IEditorPart part = r.getEditor(false); + if (part != null) { + editorOpened(part); + } + } + page.addPartListener(this); + } + + @Override + public void partActivated(IWorkbenchPart part) { + if (part instanceof AbstractTextEditor) { + AbstractTextEditor editor = (AbstractTextEditor) part; + ApplyEditorConfig apply = applies.get(editor); + if (apply != null) { + apply.applyConfig(); + } + } + } + + @Override + public void partBroughtToTop(IWorkbenchPart part) { + } + + @Override + public void partClosed(IWorkbenchPart part) { + if (part instanceof IEditorPart) { + editorClosed((IEditorPart) part); + } + } + + @Override + public void partDeactivated(IWorkbenchPart part) { + } + + @Override + public void partOpened(IWorkbenchPart part) { + if (part instanceof IEditorPart) { + editorOpened((IEditorPart) part); + } + } + + private void editorOpened(IEditorPart part) { + if (part instanceof AbstractTextEditor) { + AbstractTextEditor editor = (AbstractTextEditor) part; + ApplyEditorConfig apply = applies.get(editor); + if (apply == null) { + try { + apply = new ApplyEditorConfig(editor); + apply.install(); + applies.put(editor, apply); + } catch (Exception e) { + e.printStackTrace(); + return; + } + } + apply.applyConfig(); + } + } + + private void editorClosed(IEditorPart part) { + if (part instanceof AbstractTextEditor) { + ApplyEditorConfig apply = applies.remove(part); + if (apply != null) { + apply.uninstall(); + Assert.isTrue(null == applies.get(part), + "An old ApplyEditorConfig is not un-installed on Text Editor instance"); + } + } + } + +} diff --git a/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/codemining/EditorConfigCodeLens.java b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/codemining/EditorConfigCodeLens.java new file mode 100644 index 000000000..75cf7f120 --- /dev/null +++ b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/codemining/EditorConfigCodeLens.java @@ -0,0 +1,61 @@ +package org.eclipse.editorconfig.internal.codemining; + +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; + +import org.ec4j.core.model.Section; +import org.ec4j.core.parser.Location; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.editorconfig.search.CountSectionPatternVisitor; +import org.eclipse.editorconfig.search.EditorConfigSearchQuery; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.Position; +import org.eclipse.jface.text.codemining.ICodeMiningProvider; +import org.eclipse.jface.text.codemining.LineContentCodeMining; +import org.eclipse.jface.text.codemining.LineHeaderCodeMining; +import org.eclipse.jface.text.source.inlined.LineHeaderAnnotation; +import org.eclipse.search.ui.NewSearchUI; +import org.eclipse.swt.events.MouseEvent; + +public class EditorConfigCodeLens extends LineHeaderCodeMining { + + private Section section; + private final IFile configFile; + + public EditorConfigCodeLens(Section section, Location sectionStart, IFile configFile, + ICodeMiningProvider provider) throws BadLocationException { + super(new Position(sectionStart.getOffset(), 1), provider, null); + this.section = section; + this.configFile = configFile; + } + + @Override + public Consumer getAction() { + return (e) -> { + // Execute Search + EditorConfigSearchQuery query = new EditorConfigSearchQuery(section, configFile); + NewSearchUI.runQueryInBackground(query); + }; + } + + public Section getSection() { + return section; + } + + @Override + protected CompletableFuture doResolve(ITextViewer viewer, IProgressMonitor monitor) { + return CompletableFuture.runAsync(() -> { + CountSectionPatternVisitor visitor = new CountSectionPatternVisitor(this.getSection()); + try { + configFile.getParent().accept(visitor, IResource.NONE); + super.setLabel(visitor.getNbFiles() + " files match"); + } catch (CoreException e) { + super.setLabel(e.getMessage()); + } + }); + } +} diff --git a/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/codemining/EditorConfigCodeLensProvider.java b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/codemining/EditorConfigCodeLensProvider.java new file mode 100644 index 000000000..d2ef14b56 --- /dev/null +++ b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/codemining/EditorConfigCodeLensProvider.java @@ -0,0 +1,68 @@ +package org.eclipse.editorconfig.internal.codemining; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import org.ec4j.core.Resource.Resources; +import org.ec4j.core.model.Section; +import org.ec4j.core.parser.EditorConfigParser; +import org.ec4j.core.parser.ErrorHandler; +import org.ec4j.core.parser.Location; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.editorconfig.IDEEditorConfigManager; +import org.eclipse.editorconfig.utils.EditorUtils; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.codemining.AbstractCodeMiningProvider; +import org.eclipse.jface.text.codemining.ICodeMining; +import org.eclipse.ui.texteditor.ITextEditor; + +public class EditorConfigCodeLensProvider extends AbstractCodeMiningProvider { + + @Override + public CompletableFuture> provideCodeMinings(ITextViewer viewer, + IProgressMonitor monitor) { + return CompletableFuture.supplyAsync(() -> { + monitor.isCanceled(); + ITextEditor textEditor = super.getAdapter(ITextEditor.class); + IFile file = EditorUtils.getFile(textEditor); + if (file == null) { + return Collections.emptyList(); + } + + IDocument document = viewer.getDocument(); + IDEEditorConfigManager editorConfigManager = IDEEditorConfigManager.getInstance(); + final ErrorHandler errorHandler = ErrorHandler.IGNORING; + SectionsHandler handler = new SectionsHandler(editorConfigManager.getRegistry(), + editorConfigManager.getVersion()); + EditorConfigParser parser = EditorConfigParser.default_(); + try { + parser.parse(Resources.ofString(file.getFullPath().toString(), document.get()), handler, errorHandler); + } catch (IOException e) { + /* Will not happen with Resources.ofString() */ + throw new RuntimeException(e); + } + List
sections = handler.getEditorConfig().getSections(); + List sectionLocations = handler.getSectionLocations(); + + List lenses = new ArrayList<>(sections.size()); + + for (int i = 0; i < sections.size(); i++) { + try { + lenses.add(new EditorConfigCodeLens(sections.get(i), sectionLocations.get(i), file, this)); + } catch (BadLocationException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + return lenses; + + }); + } + +} diff --git a/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/codemining/SectionWithLoc.java b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/codemining/SectionWithLoc.java new file mode 100644 index 000000000..6153dac32 --- /dev/null +++ b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/codemining/SectionWithLoc.java @@ -0,0 +1,27 @@ +package org.eclipse.editorconfig.internal.codemining; + +import org.ec4j.core.model.Section; +import org.ec4j.core.parser.Location; + +public class SectionWithLoc { + + public SectionWithLoc(Location start, Section section) { + super(); + this.start = start; + this.section = section; + } + + + private final Location start; + private final Section section; + + + public Location getStart() { + return start; + } + + + public Section getSection() { + return section; + } +} diff --git a/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/codemining/SectionsHandler.java b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/codemining/SectionsHandler.java new file mode 100644 index 000000000..36d019279 --- /dev/null +++ b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/codemining/SectionsHandler.java @@ -0,0 +1,32 @@ +package org.eclipse.editorconfig.internal.codemining; + +import java.util.ArrayList; +import java.util.List; + +import org.ec4j.core.PropertyTypeRegistry; +import org.ec4j.core.model.Version; +import org.ec4j.core.parser.EditorConfigModelHandler; +import org.ec4j.core.parser.ErrorHandler; +import org.ec4j.core.parser.Location; +import org.ec4j.core.parser.ParseContext; + +public class SectionsHandler extends EditorConfigModelHandler { + + + public SectionsHandler(PropertyTypeRegistry registry, Version version) { + super(registry, version); + } + + private final List sectionLocations = new ArrayList<>(); + + public List getSectionLocations() { + return sectionLocations; + } + + @Override + public void startSection(ParseContext context) { + super.startSection(context); + sectionLocations.add(context.getLocation()); + } + +} diff --git a/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/completion/EditorConfigCompletionProposal.java b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/completion/EditorConfigCompletionProposal.java new file mode 100644 index 000000000..5fdf1e910 --- /dev/null +++ b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/completion/EditorConfigCompletionProposal.java @@ -0,0 +1,440 @@ +/** + * Copyright (c) 2017 Angelo ZERR. + * 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 + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Angelo Zerr - initial API and implementation + */ +package org.eclipse.editorconfig.internal.completion; + +import org.ec4j.core.ide.TokenContext.TokenContextType; +import org.ec4j.core.ide.completion.CompletionEntry; +import org.ec4j.core.model.PropertyType; +import org.eclipse.editorconfig.internal.EditorConfigImages; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.BadPositionCategoryException; +import org.eclipse.jface.text.DocumentEvent; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IPositionUpdater; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.Region; +import org.eclipse.jface.text.contentassist.BoldStylerProvider; +import org.eclipse.jface.text.contentassist.CompletionProposal; +import org.eclipse.jface.text.contentassist.ICompletionProposal; +import org.eclipse.jface.text.contentassist.ICompletionProposalExtension; +import org.eclipse.jface.text.contentassist.ICompletionProposalExtension2; +import org.eclipse.jface.text.contentassist.ICompletionProposalExtension7; +import org.eclipse.jface.text.contentassist.IContextInformation; +import org.eclipse.jface.text.link.ILinkedModeListener; +import org.eclipse.jface.text.link.InclusivePositionUpdater; +import org.eclipse.jface.text.link.LinkedModeModel; +import org.eclipse.jface.text.link.LinkedModeUI; +import org.eclipse.jface.text.link.LinkedModeUI.ExitFlags; +import org.eclipse.jface.text.link.LinkedModeUI.IExitPolicy; +import org.eclipse.jface.text.link.LinkedPositionGroup; +import org.eclipse.jface.text.link.ProposalPosition; +import org.eclipse.jface.viewers.StyledString; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.VerifyEvent; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; +import org.eclipse.ui.texteditor.link.EditorLinkedModeUI; + +/** + * Completion proposal for .editorconfig option names, values. + * + */ +public class EditorConfigCompletionProposal implements ICompletionProposal, + ICompletionProposalExtension, ICompletionProposalExtension2, ICompletionProposalExtension7 { + + private StyledString fDisplayString; + private String fReplacementString; + private int cursorPosition; + private int replacementOffset; + private int replacementlength; + private ITextViewer fTextViewer; + private boolean fToggleEating; + + private IRegion fSelectedRegion; + private IPositionUpdater fUpdater; + private final CompletionEntry completionEntry; + + public EditorConfigCompletionProposal(CompletionEntry completionEntry) { + this.completionEntry = completionEntry; + } + + @Override + public void apply(IDocument document) { + initIfNeeded(); + CompletionProposal proposal = new CompletionProposal(getReplacementString(), getReplacementOffset(), + getReplacementLength(), getCursorPosition(), getImage(), getDisplayString(), getContextInformation(), + getAdditionalProposalInfo()); + proposal.apply(document); + } + + @Override + public void apply(ITextViewer viewer, char trigger, int stateMask, int offset) { + initIfNeeded(); + IDocument document = viewer.getDocument(); + if (fTextViewer == null) { + fTextViewer = viewer; + } + // don't eat if not in preferences, XOR with modifier key 1 (Ctrl) + // but: if there is a selection, replace it! + Point selection = viewer.getSelectedRange(); + fToggleEating = (stateMask & SWT.MOD1) != 0; + int newLength = selection.x + selection.y - getReplacementOffset(); + if ((insertCompletion() ^ fToggleEating) && newLength >= 0) { + setReplacementLength(newLength); + } + apply(document, trigger, offset); + fToggleEating = false; + } + + @Override + public void apply(IDocument document, char trigger, int offset) { + initIfNeeded(); + // compute replacement string + String replacement = computeReplacementString(document, offset); + setReplacementString(replacement); + + // apply the replacement. + CompletionProposal proposal = new CompletionProposal(getReplacementString(), getReplacementOffset(), + getReplacementLength(), getCursorPosition(), getImage(), getDisplayString(), getContextInformation(), + getAdditionalProposalInfo()); + // we currently don't do anything special for which character + // selected the proposal, and where the cursor offset is + // but we might in the future... + proposal.apply(document); + int baseOffset = getReplacementOffset(); + + PropertyType optionType = completionEntry.getPropertyType(); + String[] possibleValues = optionType.getPossibleValues().toArray(new String[0]); + if (completionEntry.getContextType() == TokenContextType.PROPERTY_NAME && possibleValues != null + && getTextViewer() != null) { + + try { + LinkedModeModel model = new LinkedModeModel(); + LinkedPositionGroup group = new LinkedPositionGroup(); + + OptionValue value = new OptionValue(replacement.length() - possibleValues[0].length(), + optionType); + value.updateOffset(baseOffset); + + ensurePositionCategoryInstalled(document, model); + document.addPosition(getCategory(), value); + group.addPosition(new ProposalPosition(document, value.getOffset(), value.getLength(), + LinkedPositionGroup.NO_STOP, value.getProposals())); + model.addGroup(group); + + model.forceInstall(); + + LinkedModeUI ui = new EditorLinkedModeUI(model, getTextViewer()); + ui.setExitPosition(getTextViewer(), baseOffset + replacement.length(), 0, Integer.MAX_VALUE); + ui.setExitPolicy(new ExitPolicy(')', document)); + ui.setDoContextInfo(true); + ui.setCyclingMode(LinkedModeUI.CYCLE_WHEN_NO_PARENT); + ui.enter(); + + fSelectedRegion = ui.getSelectedRegion(); + + } catch (BadLocationException e) { + ensurePositionCategoryRemoved(document); + // JavaScriptPlugin.log(e); + // openErrorDialog(e); + } catch (BadPositionCategoryException e) { + ensurePositionCategoryRemoved(document); + // JavaScriptPlugin.log(e); + // openErrorDialog(e); + } + + } else { + int newOffset = baseOffset + replacement.length(); + fSelectedRegion = new Region(newOffset, 0); + } + } + + private ITextViewer getTextViewer() { + return fTextViewer; + } + + private String computeReplacementString(IDocument document, int offset) { + if (completionEntry.getContextType() == TokenContextType.PROPERTY_NAME) { + String first = completionEntry.getPropertyType().getPossibleValues().iterator().next(); + return new StringBuilder(getReplacementString()).append(" = ").append(first).toString(); + } + return getReplacementString(); + } + + private boolean insertCompletion() { + return true; + } + + @Override + public Point getSelection(IDocument document) { + initIfNeeded(); + if (fSelectedRegion == null) { + return new Point(getReplacementOffset(), 0); + } + return new Point(fSelectedRegion.getOffset(), fSelectedRegion.getLength()); + } + + @Override + public String getAdditionalProposalInfo() { + initIfNeeded(); + PropertyType optionType = completionEntry.getPropertyType(); + return optionType != null ? optionType.getDescription() : null; + } + + @Override + public String getDisplayString() { + initIfNeeded(); + return completionEntry.getName(); + } + + @Override + public Image getImage() { + switch (completionEntry.getContextType()) { + case PROPERTY_NAME: + return EditorConfigImages.getImage(EditorConfigImages.IMG_PROPERTY); + case PROPERTY_VALUE: + return EditorConfigImages.getImage(EditorConfigImages.IMG_VALUE); + default: + return null; + } + } + + @Override + public IContextInformation getContextInformation() { + return null; + } + + @Override + public StyledString getStyledDisplayString(IDocument document, int offset, BoldStylerProvider boldStylerProvider) { + // Highlight matched prefix + StyledString styledDisplayString = new StyledString(); + styledDisplayString.append(getStyledDisplayString()); + + String pattern = getPatternToEmphasizeMatch(document, offset); + if (pattern != null && pattern.length() > 0) { + String displayString = styledDisplayString.getString(); + int[] bestSequence = completionEntry.getMatcher().bestSubsequence(displayString, pattern); + int highlightAdjustment = 0; + for (int index : bestSequence) { + styledDisplayString.setStyle(index + highlightAdjustment, 1, boldStylerProvider.getBoldStyler()); + } + } + return styledDisplayString; + } + + public StyledString getStyledDisplayString() { + initIfNeeded(); + return fDisplayString; + } + + private void initIfNeeded() { + if (getReplacementString() != null) { + return; + } + String name = completionEntry.getName(); + String prefix = completionEntry.getPrefix(); + setReplacementString(name); + setCursorPosition(name.length()); + setReplacementOffset(completionEntry.getInitialOffset() - prefix.length()); + setReplacementLength(prefix.length()); + this.fDisplayString = new StyledString(name); + } + + private void setReplacementLength(int replacementlength) { + this.replacementlength = replacementlength; + } + + private String getReplacementString() { + return fReplacementString; + } + + private void setReplacementString(String replacementString) { + fReplacementString = replacementString; + } + + private int getReplacementLength() { + return replacementlength; + } + + private int getReplacementOffset() { + return replacementOffset; + } + + private void setReplacementOffset(int replacementOffset) { + this.replacementOffset = replacementOffset; + } + + private int getCursorPosition() { + return cursorPosition; + } + + private void setCursorPosition(int cursorPosition) { + this.cursorPosition = cursorPosition; + } + + private String getPatternToEmphasizeMatch(IDocument document, int offset) { + int start = getPrefixCompletionStart(document, offset); + int patternLength = offset - start; + String pattern = null; + try { + pattern = document.get(start, patternLength); + } catch (BadLocationException e) { + // return null + } + return pattern; + } + + private int getPrefixCompletionStart(IDocument document, int completionOffset) { + initIfNeeded(); + return replacementOffset; + } + + @Override + public boolean isValidFor(IDocument document, int offset) { + return false; // validate(document, offset, null); + } + + @Override + public char[] getTriggerCharacters() { + return null; + } + + @Override + public int getContextInformationPosition() { + initIfNeeded(); + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=110355 + // return getCursorPosition(); + if (getContextInformation() == null) + return getReplacementOffset() - 1; + return getReplacementOffset() + getCursorPosition(); + } + + @Override + public void selected(ITextViewer viewer, boolean smartToggle) { + + } + + @Override + public void unselected(ITextViewer viewer) { + + } + + @Override + public boolean validate(IDocument document, int offset, DocumentEvent event) { + initIfNeeded(); + if (offset < replacementOffset) { + return false; + } + + int replacementOffset = getReplacementOffset(); + String word = getReplacementString(); + int wordLength = word == null ? 0 : word.length(); + if (offset > replacementOffset + wordLength) { + return false; + } + + try { + int length = offset - replacementOffset; + String start = document.get(replacementOffset, length); + return completionEntry.updatePrefix(start); + } catch (BadLocationException x) { + } + + return false; + } + + private void ensurePositionCategoryInstalled(final IDocument document, LinkedModeModel model) { + if (!document.containsPositionCategory(getCategory())) { + document.addPositionCategory(getCategory()); + fUpdater = new InclusivePositionUpdater(getCategory()); + document.addPositionUpdater(fUpdater); + + model.addLinkingListener(new ILinkedModeListener() { + + /* + * @see org.eclipse.jface.text.link.ILinkedModeListener#left(org. + * eclipse.jface.text.link.LinkedModeModel, int) + */ + @Override + public void left(LinkedModeModel environment, int flags) { + ensurePositionCategoryRemoved(document); + } + + @Override + public void suspend(LinkedModeModel environment) { + } + + @Override + public void resume(LinkedModeModel environment, int flags) { + } + }); + } + } + + private void ensurePositionCategoryRemoved(IDocument document) { + if (document.containsPositionCategory(getCategory())) { + try { + document.removePositionCategory(getCategory()); + } catch (BadPositionCategoryException e) { + // ignore + } + document.removePositionUpdater(fUpdater); + } + } + + protected static final class ExitPolicy implements IExitPolicy { + + final char fExitCharacter; + private final IDocument fDocument; + + public ExitPolicy(char exitCharacter, IDocument document) { + fExitCharacter = exitCharacter; + fDocument = document; + } + + @Override + public ExitFlags doExit(LinkedModeModel environment, VerifyEvent event, int offset, int length) { + + if (event.character == fExitCharacter) { + if (environment.anyPositionContains(offset)) + return new ExitFlags(ILinkedModeListener.UPDATE_CARET, false); + else + return new ExitFlags(ILinkedModeListener.UPDATE_CARET, true); + } + + switch (event.character) { + case ';': + return new ExitFlags(ILinkedModeListener.NONE, true); + case SWT.CR: + // when entering an anonymous class as a parameter, we don't + // want + // to jump after the parenthesis when return is pressed + if (offset > 0) { + try { + if (fDocument.getChar(offset - 1) == '{') + return new ExitFlags(ILinkedModeListener.EXIT_ALL, true); + } catch (BadLocationException e) { + } + } + return null; + default: + return null; + } + } + + } + + private String getCategory() { + return "EditorConfig_" + toString(); //$NON-NLS-1$ + } + +} diff --git a/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/completion/EditorConfigContentAssistProcessor.java b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/completion/EditorConfigContentAssistProcessor.java new file mode 100644 index 000000000..e648e97b1 --- /dev/null +++ b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/completion/EditorConfigContentAssistProcessor.java @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2015-2017 Angelo ZERR. + * 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 + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Angelo Zerr - initial API and implementation + */ +package org.eclipse.editorconfig.internal.completion; + +import org.ec4j.core.ide.completion.CompletionEntryMatcher; +import org.eclipse.editorconfig.IDEEditorConfigManager; +import org.eclipse.editorconfig.internal.resource.DocumentRandomReader; +import org.eclipse.jface.text.IDocument; +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; + +/** + * Content assist processor for .editorconfig option names, values. + * + */ +public class EditorConfigContentAssistProcessor implements IContentAssistProcessor { + + private static final char[] AUTO_ACTIVATION_CHARACTERS = new char[] { '=' }; + + @Override + public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset) { + IDocument document = viewer.getDocument(); + try { + return IDEEditorConfigManager.INSTANCE.getIdeSupportService().getCompletionEntries(offset, new DocumentRandomReader(document), CompletionEntryMatcher.LCS) + .stream().map(e -> new EditorConfigCompletionProposal(e)).toArray(ICompletionProposal[]::new); + } catch (Exception e) { + } + return null; + } + + @Override + public IContextInformation[] computeContextInformation(ITextViewer viewer, int offset) { + return null; + } + + @Override + public char[] getCompletionProposalAutoActivationCharacters() { + return AUTO_ACTIVATION_CHARACTERS; + } + + @Override + public char[] getContextInformationAutoActivationCharacters() { + return null; + } + + @Override + public String getErrorMessage() { + return null; + } + + @Override + public IContextInformationValidator getContextInformationValidator() { + return null; + } + +} diff --git a/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/completion/OptionValue.java b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/completion/OptionValue.java new file mode 100644 index 000000000..33ae53ce9 --- /dev/null +++ b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/completion/OptionValue.java @@ -0,0 +1,70 @@ +/** + * Copyright (c) 2017 Angelo ZERR. + * 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 + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Angelo Zerr - initial API and implementation + */ +package org.eclipse.editorconfig.internal.completion; + +import java.util.ArrayList; +import java.util.List; + +import org.ec4j.core.model.PropertyType; +import org.eclipse.jface.text.Position; +import org.eclipse.jface.text.contentassist.ICompletionProposal; +import org.eclipse.swt.graphics.Image; + +/** + * Position for option value. + * + */ +public class OptionValue extends Position { + + private static final ICompletionProposal[] EMPTY_PROPOSALS = new ICompletionProposal[0]; + + private final String name; + private List proposals; + + public OptionValue(int offset, int length) { + this(offset, length, null); + } + + public OptionValue(int offset, int length, String name) { + super(offset, length); + this.name = name; + } + + public OptionValue(int offset, PropertyType optionType) { + this(offset, optionType.getPossibleValues().iterator().next().length()); + for (String value : optionType.getPossibleValues()) { + addProposal(value, value, null, null); + } + } + + public String getName() { + return name; + } + + public ICompletionProposal[] getProposals() { + if (proposals == null) { + return null; + } + return proposals.toArray(EMPTY_PROPOSALS); + } + + public void addProposal(String name, String displayName, Image image, String doc) { + if (proposals == null) { + proposals = new ArrayList(); + } + + proposals.add(new PositionBasedCompletionProposal(name, this, getLength(), image, displayName, null, doc)); + } + + public void updateOffset(int baseOffset) { + this.offset = baseOffset + offset; + } +} diff --git a/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/completion/PositionBasedCompletionProposal.java b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/completion/PositionBasedCompletionProposal.java new file mode 100644 index 000000000..513e114d3 --- /dev/null +++ b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/completion/PositionBasedCompletionProposal.java @@ -0,0 +1,237 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 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 + * http://www.eclipse.org/legal/epl-v10.html +*/ +package org.eclipse.editorconfig.internal.completion; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.DocumentEvent; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.Position; +import org.eclipse.jface.text.contentassist.ICompletionProposal; +import org.eclipse.jface.text.contentassist.ICompletionProposalExtension; +import org.eclipse.jface.text.contentassist.ICompletionProposalExtension2; +import org.eclipse.jface.text.contentassist.IContextInformation; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; + +/** + * An enhanced implementation of the ICompletionProposal interface + * implementing all the extension interfaces. It uses a position to track its + * replacement offset and length. The position must be set up externally. + */ +public class PositionBasedCompletionProposal implements ICompletionProposal, + ICompletionProposalExtension, ICompletionProposalExtension2 { + + /** The string to be displayed in the completion proposal popup */ + private String fDisplayString; + /** The replacement string */ + private String fReplacementString; + /** The replacement position. */ + private Position fReplacementPosition; + /** The cursor position after this proposal has been applied */ + private int fCursorPosition; + /** The image to be displayed in the completion proposal popup */ + private Image fImage; + /** The context information of this proposal */ + private IContextInformation fContextInformation; + /** The additional info of this proposal */ + private String fAdditionalProposalInfo; + + /** + * Creates a new completion proposal based on the provided information. The + * replacement string is considered being the display string too. All + * remaining fields are set to null. + * + * @param replacementString + * the actual string to be inserted into the document + * @param replacementPosition + * the position of the text to be replaced + * @param cursorPosition + * the position of the cursor following the insert relative to + * replacementOffset + */ + public PositionBasedCompletionProposal(String replacementString, + Position replacementPosition, int cursorPosition) { + this(replacementString, replacementPosition, cursorPosition, null, + null, null, null); + } + + /** + * Creates a new completion proposal. All fields are initialized based on + * the provided information. + * + * @param replacementString + * the actual string to be inserted into the document + * @param replacementPosition + * the position of the text to be replaced + * @param cursorPosition + * the position of the cursor following the insert relative to + * replacementOffset + * @param image + * the image to display for this proposal + * @param displayString + * the string to be displayed for the proposal + * @param contextInformation + * the context information associated with this proposal + * @param additionalProposalInfo + * the additional information associated with this proposal + */ + public PositionBasedCompletionProposal(String replacementString, + Position replacementPosition, int cursorPosition, Image image, + String displayString, IContextInformation contextInformation, + String additionalProposalInfo) { + Assert.isNotNull(replacementString); + Assert.isTrue(replacementPosition != null); + + fReplacementString = replacementString; + fReplacementPosition = replacementPosition; + fCursorPosition = cursorPosition; + fImage = image; + fDisplayString = displayString; + fContextInformation = contextInformation; + fAdditionalProposalInfo = additionalProposalInfo; + } + + /* + * @see ICompletionProposal#apply(IDocument) + */ + public void apply(IDocument document) { + try { + document.replace(fReplacementPosition.getOffset(), + fReplacementPosition.getLength(), fReplacementString); + } catch (BadLocationException x) { + // ignore + } + } + + /* + * @see ICompletionProposal#getSelection(IDocument) + */ + public Point getSelection(IDocument document) { + return new Point(fReplacementPosition.getOffset() + fCursorPosition, 0); + } + + /* + * @see ICompletionProposal#getContextInformation() + */ + public IContextInformation getContextInformation() { + return fContextInformation; + } + + /* + * @see ICompletionProposal#getImage() + */ + public Image getImage() { + return fImage; + } + + /* + * @see + * org.eclipse.jface.text.contentassist.ICompletionProposal#getDisplayString + * () + */ + public String getDisplayString() { + if (fDisplayString != null) + return fDisplayString; + return fReplacementString; + } + + /* + * @see ICompletionProposal#getAdditionalProposalInfo() + */ + public String getAdditionalProposalInfo() { + return fAdditionalProposalInfo; + } + + /* + * @see + * org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#apply + * (org.eclipse.jface.text.ITextViewer, char, int, int) + */ + public void apply(ITextViewer viewer, char trigger, int stateMask, + int offset) { + apply(viewer.getDocument()); + } + + /* + * @see + * org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#selected + * (org.eclipse.jface.text.ITextViewer, boolean) + */ + public void selected(ITextViewer viewer, boolean smartToggle) { + } + + /* + * @see + * org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#unselected + * (org.eclipse.jface.text.ITextViewer) + */ + public void unselected(ITextViewer viewer) { + } + + /* + * @see + * org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#validate + * (org.eclipse.jface.text.IDocument, int, + * org.eclipse.jface.text.DocumentEvent) + */ + @Override + public boolean validate(IDocument document, int offset, DocumentEvent event) { + try { + String content = document.get(fReplacementPosition.getOffset(), offset - fReplacementPosition.getOffset()); + if (fReplacementString.startsWith(content)) { + return true; + } else if (fReplacementString.length() > 0) { + char c = fReplacementString.charAt(0); + if ((c == '"' || c == '\'') && fReplacementString.startsWith(c + content)) { + return true; + } + } + } catch (BadLocationException e) { + // ignore concurrently modified document + } + return false; + } + + /* + * @see + * org.eclipse.jface.text.contentassist.ICompletionProposalExtension#apply + * (org.eclipse.jface.text.IDocument, char, int) + */ + public void apply(IDocument document, char trigger, int offset) { + // not called any more + } + + /* + * @see + * org.eclipse.jface.text.contentassist.ICompletionProposalExtension#isValidFor + * (org.eclipse.jface.text.IDocument, int) + */ + public boolean isValidFor(IDocument document, int offset) { + // not called any more + return false; + } + + /* + * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension# + * getTriggerCharacters() + */ + public char[] getTriggerCharacters() { + return null; + } + + /* + * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension# + * getContextInformationPosition() + */ + public int getContextInformationPosition() { + return fReplacementPosition.getOffset(); + } + +} diff --git a/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/folding/EditorConfigFoldingStrategy.java b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/folding/EditorConfigFoldingStrategy.java new file mode 100644 index 000000000..34b25b715 --- /dev/null +++ b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/folding/EditorConfigFoldingStrategy.java @@ -0,0 +1,176 @@ +/** + * Copyright (c) 2017 Angelo ZERR. + * 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 + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Angelo Zerr - initial API and implementation + */ +package org.eclipse.editorconfig.internal.folding; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.ec4j.core.EditorConfigConstants; +import org.ec4j.core.PropertyTypeRegistry; +import org.ec4j.core.Resource.Resources; +import org.ec4j.core.model.EditorConfig; +import org.ec4j.core.model.Section; +import org.ec4j.core.model.Version; +import org.ec4j.core.model.Comments.CommentBlock; +import org.ec4j.core.model.Comments.CommentBlocks; +import org.ec4j.core.parser.EditorConfigModelHandler; +import org.ec4j.core.parser.EditorConfigParser; +import org.ec4j.core.parser.ErrorHandler; +import org.ec4j.core.parser.LocationAwareModelHandler; +import org.ec4j.core.parser.Span; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.Position; +import org.eclipse.jface.text.reconciler.DirtyRegion; +import org.eclipse.jface.text.reconciler.IReconciler; +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.projection.IProjectionListener; +import org.eclipse.jface.text.source.projection.ProjectionAnnotation; +import org.eclipse.jface.text.source.projection.ProjectionAnnotationModel; +import org.eclipse.jface.text.source.projection.ProjectionViewer; + +/** + * EditorConfig Folding to folds: + *
    + *
  • section
  • + *
+ * + */ +public class EditorConfigFoldingStrategy + implements IReconcilingStrategy, IReconcilingStrategyExtension, IReconciler, IProjectionListener { + + private Annotation[] oldAnnotations; + private ProjectionViewer viewer; + private ProjectionAnnotationModel projectionAnnotationModel; + private IDocument document; + + @Override + public void install(ITextViewer textViewer) { + if (!(textViewer instanceof ProjectionViewer)) { + return; + } + if (viewer != null) { + viewer.removeProjectionListener(this); + } + viewer = (ProjectionViewer) textViewer; + viewer.addProjectionListener(this); + this.projectionAnnotationModel = viewer.getProjectionAnnotationModel(); + } + + @Override + public void uninstall() { + setDocument(null); + + if (viewer != null) { + viewer.removeProjectionListener(this); + viewer = null; + } + + projectionDisabled(); + } + + @Override + public IReconcilingStrategy getReconcilingStrategy(String contentType) { + return null; + } + + @Override + public void setProgressMonitor(IProgressMonitor monitor) { + + } + + @Override + public void initialReconcile() { + updateFolding(); + } + + @Override + public void setDocument(IDocument document) { + this.document = document; + } + + @Override + public void reconcile(DirtyRegion dirtyRegion, IRegion subRegion) { + updateFolding(); + } + + private void updateFolding() { + EditorConfig editorConfig = parse(document); + updateFolding(editorConfig); + } + + private void updateFolding(EditorConfig editorConfig) { + if (projectionAnnotationModel == null) { + return; + } + List
sections = editorConfig.getSections(); + CommentBlocks commentBlocks = editorConfig.getAdapter(CommentBlocks.class); + List comments = commentBlocks != null ? commentBlocks.getCommentBlocks() + : Collections.emptyList(); + Map newAnnotations = new HashMap<>(); + // Collection section and comment spans; + List spans = /*Stream.concat(sections.stream(), comments.stream())*/ + sections.stream() + .map(a -> a.getAdapter(Span.class)) + .sorted((s1, s2) -> s1.getStart().getLine() - s2.getStart().getLine()).collect(Collectors.toList()); + Annotation[] annotations = new Annotation[spans.size()]; + for (int i = 0; i < spans.size(); i++) { + Span span = spans.get(i); + int startOffset = span.getStart().getOffset(); + int endOffset = span.getEnd().getOffset(); + ProjectionAnnotation annotation = new ProjectionAnnotation(); + newAnnotations.put(annotation, new Position(startOffset, endOffset - startOffset)); + annotations[i] = annotation; + } + projectionAnnotationModel.modifyAnnotations(oldAnnotations, newAnnotations, null); + oldAnnotations = annotations; + } + + @Override + public void reconcile(IRegion partition) { + } + + private EditorConfig parse(IDocument document) { + final ErrorHandler errorHandler = ErrorHandler.THROWING; + EditorConfigModelHandler handler = new LocationAwareModelHandler(PropertyTypeRegistry.default_(), + Version.CURRENT, errorHandler); + EditorConfigParser parser = EditorConfigParser.default_(); + try { + parser.parse(Resources.ofString(EditorConfigConstants.EDITORCONFIG, document.get()), handler, + errorHandler ); + } catch (IOException e) { + e.printStackTrace(); + } + return handler.getEditorConfig(); + } + + @Override + public void projectionEnabled() { + if (viewer != null) { + projectionAnnotationModel = viewer.getProjectionAnnotationModel(); + } + } + + @Override + public void projectionDisabled() { + projectionAnnotationModel = null; + } + +} diff --git a/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/hover/EditorConfigTextHover.java b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/hover/EditorConfigTextHover.java new file mode 100644 index 000000000..65e8dbd4d --- /dev/null +++ b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/hover/EditorConfigTextHover.java @@ -0,0 +1,78 @@ +/** + * Copyright (c) 2017 Angelo ZERR. + * 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 + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Angelo Zerr - initial API and implementation + */ +package org.eclipse.editorconfig.internal.hover; + +import java.util.Iterator; + +import org.eclipse.editorconfig.IDEEditorConfigManager; +import org.eclipse.editorconfig.internal.resource.DocumentRandomReader; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.ITextHover; +import org.eclipse.jface.text.ITextViewer; +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.IAnnotationModelExtension2; +import org.eclipse.jface.text.source.ISourceViewer; +import org.eclipse.ui.texteditor.MarkerAnnotation; + +/** + * Hover to display .editorconfig option name. + * + */ +public class EditorConfigTextHover implements ITextHover { + + + @Override + public String getHoverInfo(ITextViewer textViewer, IRegion hoverRegion) { + if (hasProblem(textViewer, hoverRegion.getOffset())) { + // There are marker annotation, don't return the editorconfig hover. + return null; + } + try { + return IDEEditorConfigManager.INSTANCE.getIdeSupportService().getHover(hoverRegion.getOffset(), new DocumentRandomReader(textViewer.getDocument())); + } catch (Exception e) { + return null; + } + } + + @Override + public IRegion getHoverRegion(ITextViewer textViewer, int offset) { + return new Region(offset, 1); + } + + /*** + * Returns true if it exists a marker annotation in the given offset and false + * otherwise. + * + * @param textViewer + * @param offset + * @return true if it exists a marker annotation in the given offset and false + * otherwise. + */ + private static boolean hasProblem(ITextViewer textViewer, int offset) { + if (!(textViewer instanceof ISourceViewer)) { + return false; + } + + IAnnotationModel annotationModel = ((ISourceViewer) textViewer).getAnnotationModel(); + Iterator iter = (annotationModel instanceof IAnnotationModelExtension2) + ? ((IAnnotationModelExtension2) annotationModel).getAnnotationIterator(offset, 1, true, true) + : annotationModel.getAnnotationIterator(); + while (iter.hasNext()) { + Annotation ann = iter.next(); + if (ann instanceof MarkerAnnotation) { + return true; + } + } + return false; + } +} diff --git a/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/outline/EditorConfigContentProvider.java b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/outline/EditorConfigContentProvider.java new file mode 100644 index 000000000..12ccdb363 --- /dev/null +++ b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/outline/EditorConfigContentProvider.java @@ -0,0 +1,171 @@ +package org.eclipse.editorconfig.internal.outline; + +import java.io.IOException; +import java.util.concurrent.CompletableFuture; + +import org.ec4j.core.EditorConfigConstants; +import org.ec4j.core.PropertyTypeRegistry; +import org.ec4j.core.Resource.Resources; +import org.ec4j.core.model.EditorConfig; +import org.ec4j.core.model.Property; +import org.ec4j.core.model.Section; +import org.ec4j.core.model.Version; +import org.ec4j.core.parser.EditorConfigModelHandler; +import org.ec4j.core.parser.EditorConfigParser; +import org.ec4j.core.parser.ErrorHandler; +import org.ec4j.core.parser.LocationAwareModelHandler; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IResourceChangeEvent; +import org.eclipse.core.resources.IResourceChangeListener; +import org.eclipse.core.resources.IResourceDelta; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.editorconfig.utils.EditorUtils; +import org.eclipse.jface.text.DocumentEvent; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IDocumentListener; +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.StructuredViewer; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.ui.IMemento; +import org.eclipse.ui.navigator.ICommonContentExtensionSite; +import org.eclipse.ui.navigator.ICommonContentProvider; +import org.eclipse.ui.texteditor.ITextEditor; + +public class EditorConfigContentProvider + implements ICommonContentProvider, ITreeContentProvider, IDocumentListener, IResourceChangeListener { + + public static final Object COMPUTING = new Object(); + + private TreeViewer viewer; + private ITextEditor info; + private IFile resource; + private EditorConfig editorConfig; + private CompletableFuture symbols; + + @Override + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + this.viewer = (TreeViewer) viewer; + this.info = (ITextEditor) newInput; + getDocument().addDocumentListener(this); + resource = EditorUtils.getFile(info); + resource.getWorkspace().addResourceChangeListener(this, IResourceChangeEvent.POST_CHANGE); + refreshTreeContentFromLS(); + } + + public IDocument getDocument() { + return info.getDocumentProvider().getDocument(info.getEditorInput()); + } + + @Override + public void documentAboutToBeChanged(DocumentEvent event) { + } + + @Override + public void documentChanged(DocumentEvent event) { + refreshTreeContentFromLS(); + } + + private void refreshTreeContentFromLS() { + if (symbols != null && !symbols.isDone()) { + symbols.cancel(true); + } + symbols = CompletableFuture.supplyAsync(() -> { + this.editorConfig = parse(getDocument()); + return editorConfig; + }); + symbols.thenAccept(e -> { + viewer.getControl().getDisplay().asyncExec(() -> { + viewer.refresh(); + }); + }); + } + + private EditorConfig parse(IDocument document) { + final ErrorHandler errorHandler = ErrorHandler.THROWING; + EditorConfigModelHandler handler = new LocationAwareModelHandler(PropertyTypeRegistry.default_(), + Version.CURRENT, errorHandler); + EditorConfigParser parser = EditorConfigParser.default_(); + try { + parser.parse(Resources.ofString(EditorConfigConstants.EDITORCONFIG, document.get()), handler, errorHandler); + } catch (IOException e) { + e.printStackTrace(); + } + return handler.getEditorConfig(); + } + + @Override + public Object[] getElements(Object inputElement) { + if (this.symbols != null && !this.symbols.isDone()) { + return new Object[] { COMPUTING }; + } + if (editorConfig != null) { + return editorConfig.getSections().toArray(); + } + return null; + } + + @Override + public Object[] getChildren(Object parentElement) { + if (parentElement instanceof Section) { + return ((Section) parentElement).getProperties().values().toArray(); + } + return null; + } + + @Override + public Object getParent(Object element) { + if (element instanceof Property) { + // return ((Property) element). + } + return null; + } + + @Override + public boolean hasChildren(Object element) { + if (element instanceof Section) { + return ((Section) element).getProperties().size() > 0; + } + return false; + } + + @Override + public void dispose() { + getDocument().removeDocumentListener(this); + resource.getWorkspace().removeResourceChangeListener(this); + ICommonContentProvider.super.dispose(); + } + + @Override + public void resourceChanged(IResourceChangeEvent event) { + if ((event.getDelta().getFlags() ^ IResourceDelta.MARKERS) != 0) { + try { + event.getDelta().accept(delta -> { + if (delta.getResource().equals(this.resource)) { + viewer.getControl().getDisplay().asyncExec(() -> { + if (viewer instanceof StructuredViewer) { + viewer.refresh(true); + } + }); + } + return delta.getResource().getFullPath().isPrefixOf(this.resource.getFullPath()); + }); + } catch (CoreException e) { + // LanguageServerPlugin.logError(e); + } + } + } + + @Override + public void init(ICommonContentExtensionSite aConfig) { + } + + @Override + public void restoreState(IMemento aMemento) { + } + + @Override + public void saveState(IMemento aMemento) { + } + +} diff --git a/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/outline/EditorConfigLabelProvider.java b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/outline/EditorConfigLabelProvider.java new file mode 100644 index 000000000..82c1826ba --- /dev/null +++ b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/outline/EditorConfigLabelProvider.java @@ -0,0 +1,48 @@ +package org.eclipse.editorconfig.internal.outline; + +import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider.IStyledLabelProvider; +import org.ec4j.core.model.Property; +import org.ec4j.core.model.Section; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.jface.viewers.StyledString; +import org.eclipse.ui.IMemento; +import org.eclipse.ui.navigator.ICommonContentExtensionSite; +import org.eclipse.ui.navigator.ICommonLabelProvider; + +public class EditorConfigLabelProvider extends LabelProvider implements ICommonLabelProvider, IStyledLabelProvider { + + @Override + public void restoreState(IMemento aMemento) { + // TODO Auto-generated method stub + + } + + @Override + public void saveState(IMemento aMemento) { + // TODO Auto-generated method stub + + } + + @Override + public String getDescription(Object anElement) { + // TODO Auto-generated method stub + return null; + } + + @Override + public StyledString getStyledText(Object element) { + if (element instanceof Section) { + return new StyledString(((Section) element).getGlob().getSource()); + } else if (element instanceof Property) { + return new StyledString(((Property) element).toString()); + } + return null; + } + + @Override + public void init(ICommonContentExtensionSite aConfig) { + // TODO Auto-generated method stub + + } + +} diff --git a/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/outline/EditorConfigOutlinePage.java b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/outline/EditorConfigOutlinePage.java new file mode 100644 index 000000000..ec315499d --- /dev/null +++ b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/outline/EditorConfigOutlinePage.java @@ -0,0 +1,69 @@ +package org.eclipse.editorconfig.internal.outline; + +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.ui.IActionBars; +import org.eclipse.ui.navigator.CommonViewer; +import org.eclipse.ui.texteditor.ITextEditor; +import org.eclipse.ui.views.contentoutline.IContentOutlinePage; + +public class EditorConfigOutlinePage implements IContentOutlinePage { + + public static final String ID = "org.eclipse.editorconfig.outline"; //$NON-NLS-1$ + private CommonViewer viewer; + + private final ITextEditor editor; + + public EditorConfigOutlinePage(ITextEditor editor) { + this.editor = editor; + } + + @Override + public void createControl(Composite parent) { + this.viewer = new CommonViewer(ID, parent, SWT.NONE); + viewer.setInput(editor); + } + + @Override + public void dispose() { + this.viewer.dispose(); + } + + @Override + public Control getControl() { + return this.viewer.getControl(); + } + + @Override + public void setActionBars(IActionBars actionBars) { + } + + @Override + public void setFocus() { + this.viewer.getTree().setFocus(); + } + + @Override + public void addSelectionChangedListener(ISelectionChangedListener listener) { + this.viewer.addSelectionChangedListener(listener); + } + + @Override + public ISelection getSelection() { + return this.viewer.getSelection(); + } + + @Override + public void removeSelectionChangedListener(ISelectionChangedListener listener) { + this.viewer.removeSelectionChangedListener(listener); + } + + @Override + public void setSelection(ISelection selection) { + this.viewer.setSelection(selection); + } + +} diff --git a/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/outline/EditorConfigToOutlineAdapterFactory.java b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/outline/EditorConfigToOutlineAdapterFactory.java new file mode 100644 index 000000000..6f741f7a8 --- /dev/null +++ b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/outline/EditorConfigToOutlineAdapterFactory.java @@ -0,0 +1,24 @@ +package org.eclipse.editorconfig.internal.outline; + +import org.eclipse.core.runtime.IAdapterFactory; +import org.eclipse.editorconfig.utils.EditorUtils; +import org.eclipse.ui.texteditor.ITextEditor; +import org.eclipse.ui.views.contentoutline.IContentOutlinePage; + +public class EditorConfigToOutlineAdapterFactory implements IAdapterFactory { + + @Override + public T getAdapter(Object adaptableObject, Class adapterType) { + if (adapterType == IContentOutlinePage.class && adaptableObject instanceof ITextEditor + && EditorUtils.isEditorConfigFile((ITextEditor) adaptableObject)) { + return (T) new EditorConfigOutlinePage((ITextEditor) adaptableObject); + } + return null; + } + + @Override + public Class[] getAdapterList() { + return new Class[] { IContentOutlinePage.class }; + } + +} diff --git a/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/resource/ContainerResourcePath.java b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/resource/ContainerResourcePath.java new file mode 100644 index 000000000..4a3316d22 --- /dev/null +++ b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/resource/ContainerResourcePath.java @@ -0,0 +1,65 @@ +package org.eclipse.editorconfig.internal.resource; + +import org.ec4j.core.Resource; +import org.ec4j.core.ResourcePath; +import org.ec4j.core.model.Ec4jPath; +import org.ec4j.core.model.Ec4jPath.Ec4jPaths; +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.Path; + +/** + * A {@link ResourcePath} implementation based on {@link IContainer}. + * + * @author Peter Palaga + */ +public class ContainerResourcePath implements ResourcePath { + + private final IContainer container; + + ContainerResourcePath(IContainer container) { + this.container = container; + } + + @Override + public ResourcePath getParent() { + if (container.getType() == IResource.PROJECT) { + // Stop the search of '.editorconfig' files in the parent container + return null; + } + IContainer parent = container.getParent(); + return parent == null ? null : new ContainerResourcePath(parent); + } + + @Override + public Ec4jPath getPath() { + return Ec4jPaths.of(container.getLocation().toString().replaceAll("[\\\\]", "/")); + } + + @Override + public boolean hasParent() { + IContainer parent = container.getParent(); + return parent != null; + } + + /** {@inheritDoc} */ + @Override + public Resource relativize(Resource resource) { + if (resource instanceof FileResource) { + final FileResource fileResource = (FileResource) resource; + final IFile relativeFile = container.getFile(fileResource.file.getFullPath().makeRelativeTo(container.getFullPath())); + return new FileResource(relativeFile); + } else { + throw new IllegalArgumentException(this.getClass().getName() + + ".relativize(Resource resource) can handle only instances of " + FileResource.class.getName()); + } + } + + @Override + public Resource resolve(String name) { + IFile child = container.getFile(new Path(name)); + return new FileResource(child); + } + +} \ No newline at end of file diff --git a/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/resource/DocumentRandomReader.java b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/resource/DocumentRandomReader.java new file mode 100644 index 000000000..a7b7cf7bb --- /dev/null +++ b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/resource/DocumentRandomReader.java @@ -0,0 +1,41 @@ +package org.eclipse.editorconfig.internal.resource; + +import java.io.IOException; + +import org.ec4j.core.Resource.RandomReader; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IDocument; + +/** + * A {@link RandomReader} implementation that uses an underlying {@link IDocument}. + * + * @author Peter Palaga + */ +public class DocumentRandomReader implements RandomReader { + private final IDocument document; + + public DocumentRandomReader(IDocument document) { + super(); + this.document = document; + } + + @Override + public void close() throws IOException { + /* nothing to do */ + } + + @Override + public long getLength() { + return document.getLength(); + } + + @Override + public char read(long offset) throws IndexOutOfBoundsException { + try { + return document.getChar((int)offset); + } catch (BadLocationException e) { + throw new IndexOutOfBoundsException(e.getMessage()); + } + } + +} \ No newline at end of file diff --git a/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/resource/FileResource.java b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/resource/FileResource.java new file mode 100644 index 000000000..c546874aa --- /dev/null +++ b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/resource/FileResource.java @@ -0,0 +1,61 @@ +package org.eclipse.editorconfig.internal.resource; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.nio.charset.Charset; + +import org.ec4j.core.Resource; +import org.ec4j.core.ResourcePath; +import org.ec4j.core.model.Ec4jPath; +import org.ec4j.core.model.Ec4jPath.Ec4jPaths; +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.runtime.CoreException; + +/** + * A {@link Resource} implementation that uses an underlying {@link IFile}. + * + * @author Peter Palaga + */ +public class FileResource implements Resource { + final IFile file; + + public FileResource(IFile file) { + super(); + this.file = file; + } + + @Override + public boolean exists() { + return file.exists(); + } + + @Override + public ResourcePath getParent() { + IContainer parent = file.getParent(); + return parent == null ? null : new ContainerResourcePath(parent); + } + + @Override + public Ec4jPath getPath() { + return Ec4jPaths.of(file.getLocation().toString().replaceAll("[\\\\]", "/")); + } + + @Override + public RandomReader openRandomReader() throws IOException { + try (Reader reader = openReader()) { + return org.ec4j.core.Resource.Resources.StringRandomReader.ofReader(reader); + } + } + + @Override + public Reader openReader() throws IOException { + try { + return new InputStreamReader(file.getContents(), Charset.forName(file.getCharset())); + } catch (CoreException e) { + throw new IOException(e); + } + } + +} \ No newline at end of file diff --git a/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/validation/ValidateAppliedOptionsStrategy.java b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/validation/ValidateAppliedOptionsStrategy.java new file mode 100644 index 000000000..4c138b76d --- /dev/null +++ b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/validation/ValidateAppliedOptionsStrategy.java @@ -0,0 +1,381 @@ +/** + * Copyright (c) 2017 Angelo ZERR. + * 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 + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Angelo Zerr - initial API and implementation + */ +package org.eclipse.editorconfig.internal.validation; + +import static org.eclipse.editorconfig.internal.EditorConfigPreferenceStore.EDITOR_INSERT_FINAL_NEWLINE; +import static org.eclipse.editorconfig.internal.EditorConfigPreferenceStore.EDITOR_TRIM_TRAILING_WHITESPACE; + +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; + +import org.ec4j.core.PropertyTypeRegistry; +import org.ec4j.core.model.PropertyType; +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.editorconfig.IDEEditorConfigManager; +import org.eclipse.editorconfig.internal.validation.marker.MarkerUtils; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.Region; +import org.eclipse.jface.text.reconciler.DirtyRegion; +import org.eclipse.jface.text.reconciler.IReconciler; +import org.eclipse.jface.text.reconciler.IReconcilingStrategy; +import org.eclipse.jface.text.reconciler.IReconcilingStrategyExtension; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.jface.util.PropertyChangeEvent; +import org.eclipse.ui.texteditor.MarkerUtilities; + +/** + * Validate content of any editor where .editorconfig options + * 'trim_trailing_whitespace' and/or 'insert_final_newline' must be applied. + * + */ +public class ValidateAppliedOptionsStrategy + implements IReconcilingStrategy, IReconcilingStrategyExtension, IPropertyChangeListener, IReconciler { + + // preference store & file resource of the editor to validate + private final IPreferenceStore preferenceStore; + private final IResource resource; + + // Text viewer of the editor to validate + private ITextViewer textViewer; + private final PropertyType insertFinalNewlineType; + private final PropertyType trimTrailingWhitespaceType; + + public ValidateAppliedOptionsStrategy(IPreferenceStore preferenceStore, IResource resource) { + this.preferenceStore = preferenceStore; + this.resource = resource; + this.insertFinalNewlineType = PropertyType.insert_final_newline; + this.trimTrailingWhitespaceType = PropertyType.trim_trailing_whitespace; + } + + @Override + public void reconcile(DirtyRegion dirtyRegion, IRegion subRegion) { + if (textViewer == null) { + return; + } + try { + // Collect .editorconfig markers which have option type (coming from this validator) + Set remainingMarkers = MarkerUtils.findEditorConfigMarkers(resource).stream().filter(marker -> { + try { + return MarkerUtils.getOptionType(marker) != null; + } catch (CoreException e) { + return false; + } + }).collect(Collectors.toSet()); + IDocument document = textViewer.getDocument(); + // Validate 'trim_trailing_whitespace' if needed + IRegion region = DirtyRegion.REMOVE.equals(dirtyRegion.getType()) ? new Region(subRegion.getOffset(), 0) + : subRegion; + validateTrimTrailingWhiteSpace(document, region, remainingMarkers); + // Validate 'insert_final_newline' if needed + validateInsertFinalNewline(document, remainingMarkers); + for (IMarker marker : remainingMarkers) { + marker.delete(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void propertyChange(PropertyChangeEvent event) { + if (EDITOR_TRIM_TRAILING_WHITESPACE.equals(event.getProperty())) { + // apply of .editorconfig 'trim_trailing_whitespace' option to the editor + validateOnlyTrimTrailingWhiteSpace(); + } else if (EDITOR_INSERT_FINAL_NEWLINE.equals(event.getProperty())) { + // apply of .editorconfig 'insert_final_newline' option to the editor + validateOnlyInsertFinalNewline(); + } + } + + @Override + public void install(ITextViewer textViewer) { + this.textViewer = textViewer; + preferenceStore.addPropertyChangeListener(this); + } + + @Override + public void uninstall() { + this.textViewer = null; + preferenceStore.removePropertyChangeListener(this); + } + + /** + * Validate only 'trim_trailing_whitespace'. + */ + private void validateOnlyTrimTrailingWhiteSpace() { + if (textViewer == null) { + return; + } + try { + Set remainingMarkers = MarkerUtils.findEditorConfigMarkers(resource).stream().filter(marker -> { + try { + return MarkerUtils.getOptionType(marker) == trimTrailingWhitespaceType; + } catch (CoreException e) { + return false; + } + }).collect(Collectors.toSet()); + // Validate the whole lines of the document. + IDocument document = textViewer.getDocument(); + validateTrimTrailingWhiteSpace(document, new Region(0, document.getLength()), remainingMarkers); + for (IMarker marker : remainingMarkers) { + marker.delete(); + } + } catch (CoreException | BadLocationException e) { + e.printStackTrace(); + } + } + + /** + * Validate 'trim_trailing_whitespace' of the lines where region changed if + * needed and update the given set of marker. + * + * @param partition + * the region which changed + * @param remainingMarkers + * set of markers to update. + * @throws BadLocationException + */ + private void validateTrimTrailingWhiteSpace(IDocument document, IRegion partition, Set remainingMarkers) + throws BadLocationException, CoreException { + boolean trimTrailingWhiteSpace = preferenceStore.getBoolean(EDITOR_TRIM_TRAILING_WHITESPACE); + if (!trimTrailingWhiteSpace) { + return; + } + int startLine = document.getLineOfOffset(partition.getOffset()); + int endLine = document.getLineOfOffset(partition.getOffset() + partition.getLength()); + + IRegion region = null; + for (int i = startLine; i < endLine + 1; i++) { + region = document.getLineInformation(i); + if (region.getLength() == 0) + continue; + int lineStart = region.getOffset(); + int lineExclusiveEnd = lineStart + region.getLength(); + int j = lineExclusiveEnd - 1; + while (j >= lineStart && Character.isWhitespace(document.getChar(j))) + --j; + ++j; + if (j < lineExclusiveEnd) { + addOrUpdateMarker(j, lineExclusiveEnd, trimTrailingWhitespaceType, document, remainingMarkers); + } + } + if (region != null) { + for (IMarker marker : new HashSet<>(remainingMarkers)) { + if (MarkerUtils.getOptionType(marker) == trimTrailingWhitespaceType) { + int line = MarkerUtilities.getLineNumber(marker) + 1; + if (line < startLine || line > endLine) { + remainingMarkers.remove(marker); + } + } + } + } + } + + /** + * Validate only 'insert_final_newline' + */ + private void validateOnlyInsertFinalNewline() { + if (textViewer == null) { + return; + } + try { + Set remainingMarkers = MarkerUtils.findEditorConfigMarkers(resource).stream().filter(marker -> { + try { + return MarkerUtils.getOptionType(marker) == insertFinalNewlineType; + } catch (CoreException e) { + return false; + } + }).collect(Collectors.toSet()); + IDocument document = textViewer.getDocument(); + validateInsertFinalNewline(document, remainingMarkers); + for (IMarker marker : remainingMarkers) { + marker.delete(); + } + } catch (CoreException | BadLocationException e) { + e.printStackTrace(); + } + } + + /** + * Validate 'insert_final_newline' if needed and update the given set of marker. + * + * @param document + * the document to validate + * @param remainingMarkers + * set of markers to update. + * @throws BadLocationException + */ + private void validateInsertFinalNewline(IDocument document, Set remainingMarkers) + throws BadLocationException { + boolean insertFinalNewline = preferenceStore.getBoolean(EDITOR_INSERT_FINAL_NEWLINE); + if (!insertFinalNewline) { + return; + } + // Check if there are an empty line at the end of the document. + if (document.getLength() == 0) { + return; + } + int line = document.getNumberOfLines() - 1; + IRegion region = document.getLineInformation(line); + if (region.getLength() > 0) { + int end = region.getOffset() + region.getLength(); + int start = end - 1; + addOrUpdateMarker(start, end, insertFinalNewlineType, document, remainingMarkers); + } + } + + /** + * Add or update marker error. + * + * @param start + * the start of the error. + * @param end + * the end of the error. + * @param type + * the option type where there are an error. + * @param document + * the document to validate. + * @param remainingMarkers + * set of markers to update. + */ + private void addOrUpdateMarker(int start, int end, PropertyType type, IDocument document, + Set remainingMarkers) { + try { + IMarker associatedMarker = getExistingMarkerFor(start, end, type, remainingMarkers); + if (associatedMarker == null) { + associatedMarker = MarkerUtils.createEditorConfigMarker(resource); + } else { + remainingMarkers.remove(associatedMarker); + } + updateMarker(start, end, type, document, associatedMarker); + } catch (CoreException e) { + e.printStackTrace(); + } + } + + /** + * Update marker. + * + * @param start + * the start of the error. + * @param end + * the end of the error. + * @param type + * the option type where there are an error. + * @param document + * the document to validate. + * @param marker + * the marker to update. + */ + private void updateMarker(int start, int end, PropertyType type, IDocument document, IMarker marker) { + try { + MarkerUtils.setOptionType(marker, type); + marker.setAttribute(IMarker.MESSAGE, getMessage(type)); + marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR); + if (resource.getType() != IResource.FILE) { + return; + } + marker.setAttribute(IMarker.CHAR_START, start); + marker.setAttribute(IMarker.CHAR_END, end); + marker.setAttribute(IMarker.LINE_NUMBER, document.getLineOfOffset(start) + 1); + } catch (CoreException | BadLocationException e) { + e.printStackTrace(); + } + } + + /** + * Returns the error message according the option type. + * + * @param type + * the option type. + * @return the error message according the option type. + */ + private String getMessage(PropertyType type) { + if (insertFinalNewlineType == type) { + return "Insert final newline"; + } else if (trimTrailingWhitespaceType == type) { + return "Trim traling whitespace"; + } + return null; + } + + /** + * Return the existing marker and null otherwise. + * + * @param start + * the start of the error. + * @param end + * the end of the error. + * @param type + * the option type where there are an error. + * @param remainingMarkers + * set of markers to update. + * @return the existing marker and null otherwise. + */ + private IMarker getExistingMarkerFor(int start, int end, PropertyType type, Set remainingMarkers) { + try { + if (insertFinalNewlineType == type) { + for (IMarker marker : remainingMarkers) { + if (type == MarkerUtils.getOptionType(marker)) { + return marker; + } + } + } else if (trimTrailingWhitespaceType == type) { + for (IMarker marker : remainingMarkers) { + int startOffset = MarkerUtilities.getCharStart(marker); + int endOffset = MarkerUtilities.getCharEnd(marker); + if (type == MarkerUtils.getOptionType(marker)) { + if (start <= startOffset && end >= endOffset) { + return marker; + } + } + } + } + } catch (CoreException e) { + e.printStackTrace(); + } + return null; + } + + @Override + public void setProgressMonitor(IProgressMonitor monitor) { + // Do nothing + } + + @Override + public void initialReconcile() { + // Do nothing + } + + @Override + public void setDocument(IDocument document) { + // Do nothing + } + + @Override + public IReconcilingStrategy getReconcilingStrategy(String contentType) { + return null; + } + + @Override + public void reconcile(IRegion partition) { + // Do nothing + } + +} diff --git a/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/validation/ValidateEditorConfigStrategy.java b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/validation/ValidateEditorConfigStrategy.java new file mode 100644 index 000000000..64025356a --- /dev/null +++ b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/validation/ValidateEditorConfigStrategy.java @@ -0,0 +1,185 @@ +package org.eclipse.editorconfig.internal.validation; + +import java.util.Set; +import java.util.stream.Collectors; + +import org.ec4j.core.parser.EditorConfigParser; +import org.ec4j.core.parser.ErrorEvent; +import org.ec4j.core.parser.ErrorHandler; +import org.ec4j.core.parser.ParseContext; +import org.ec4j.core.parser.ValidatingHandler; +import org.eclipse.core.filebuffers.FileBuffers; +import org.eclipse.core.filebuffers.ITextFileBuffer; +import org.eclipse.core.filebuffers.ITextFileBufferManager; +import org.eclipse.core.filebuffers.LocationKind; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.editorconfig.internal.validation.marker.MarkerUtils; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.reconciler.DirtyRegion; +import org.eclipse.jface.text.reconciler.IReconciler; +import org.eclipse.jface.text.reconciler.IReconcilingStrategy; +import org.eclipse.jface.text.reconciler.IReconcilingStrategyExtension; +import org.eclipse.ui.texteditor.MarkerUtilities; + +public class ValidateEditorConfigStrategy + implements IReconcilingStrategy, IReconcilingStrategyExtension, IReconciler { + + private ITextViewer textViewer; + + private IResource resource; + + public ValidateEditorConfigStrategy(IResource resource) { + this.resource = resource; + } + + @Override + public void setProgressMonitor(IProgressMonitor monitor) { + + } + + @Override + public void initialReconcile() { + + } + + @Override + public void setDocument(IDocument document) { + + } + + @Override + public void reconcile(DirtyRegion dirtyRegion, IRegion subRegion) { + if (textViewer == null) { + return; + } + try { + IDocument document = textViewer.getDocument(); + Set remainingMarkers = MarkerUtils.findEditorConfigMarkers(resource).stream().filter(marker -> { + try { + return MarkerUtils.getOptionType(marker) == null; + } catch (CoreException e) { + return false; + } + }).collect(Collectors.toSet()); + + org.ec4j.core.Resource ec4jResource = org.ec4j.core.Resource.Resources.ofString(resource.getFullPath().toString(), document.get()); + EditorConfigParser parser = EditorConfigParser.default_(); + ValidatingHandler handler = new ValidatingHandler(org.ec4j.core.PropertyTypeRegistry.builder().defaults().build()); + parser.parse(ec4jResource, handler, new ErrorHandler() { + @Override + public void error(ParseContext context, ErrorEvent errorEvent) { + int startOffset = errorEvent.getStart().getOffset(); + int endOffset = startOffset; + if (errorEvent.getEnd() == null) { + startOffset--; + } else { + endOffset = errorEvent.getEnd().getOffset(); + } + addError(errorEvent.getMessage(), startOffset, endOffset, MarkerUtils.getSeverity(errorEvent.getErrorType()), remainingMarkers); + } + }); + + for (IMarker marker : remainingMarkers) { + marker.delete(); + } + } catch (Exception e) { + e.printStackTrace(); + } + + } + + @Override + public void reconcile(IRegion partition) { + + } + + @Override + public void install(ITextViewer textViewer) { + this.textViewer = textViewer; + } + + @Override + public void uninstall() { + this.textViewer = null; + } + + @Override + public IReconcilingStrategy getReconcilingStrategy(String contentType) { + return null; + } + + private void addError(String message, int start, int end, int severity, + Set remainingMarkers) { + try { + IMarker associatedMarker = getExistingMarkerFor(resource, message, start, end, remainingMarkers); + if (associatedMarker == null) { + associatedMarker = MarkerUtils.createEditorConfigMarker(resource); + } else { + remainingMarkers.remove(associatedMarker); + } + updateMarker(resource, message, start, end, severity, associatedMarker); + } catch (CoreException e) { + e.printStackTrace(); + } + + } + + private void updateMarker(IResource resource, String message, int start, int end, int severity, + IMarker marker) { + try { + marker.setAttribute(IMarker.MESSAGE, message); + marker.setAttribute(IMarker.SEVERITY, severity); + if (resource.getType() != IResource.FILE) { + return; + } + IFile file = (IFile) resource; + ITextFileBufferManager manager = FileBuffers.getTextFileBufferManager(); + ITextFileBuffer textFileBuffer = manager.getTextFileBuffer(file.getFullPath(), LocationKind.IFILE); + + if (textFileBuffer == null) { + manager.connect(file.getFullPath(), LocationKind.IFILE, new NullProgressMonitor()); + textFileBuffer = manager.getTextFileBuffer(file.getFullPath(), LocationKind.IFILE); + } + + IDocument document = textFileBuffer.getDocument(); + + marker.setAttribute(IMarker.CHAR_START, start); + marker.setAttribute(IMarker.CHAR_END, end); + marker.setAttribute(IMarker.LINE_NUMBER, document.getLineOfOffset(start) + 1); + } catch (CoreException | BadLocationException e) { + e.printStackTrace(); + } + } + + private IMarker getExistingMarkerFor(IResource resource, String message, int start, int end, + Set remainingMarkers) { + ITextFileBuffer textFileBuffer = FileBuffers.getTextFileBufferManager() + .getTextFileBuffer(resource.getFullPath(), LocationKind.IFILE); + if (textFileBuffer == null) { + return null; + } + try { + for (IMarker marker : remainingMarkers) { + int startOffset = MarkerUtilities.getCharStart(marker); + int endOffset = MarkerUtilities.getCharEnd(marker); + if (MarkerUtils.getOptionType(marker) == null && message.equals(MarkerUtilities.getMessage(marker))) { + if (start <= startOffset && end >= endOffset) { + return marker; + } + } + } + } catch (CoreException e) { + e.printStackTrace(); + } + return null; + } + +} diff --git a/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/validation/marker/AbstractMarkerResolution.java b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/validation/marker/AbstractMarkerResolution.java new file mode 100644 index 000000000..b4322cc76 --- /dev/null +++ b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/validation/marker/AbstractMarkerResolution.java @@ -0,0 +1,71 @@ +/** + * Copyright (c) 2017 Angelo ZERR. + * 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 + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Angelo Zerr - initial API and implementation + */ +package org.eclipse.editorconfig.internal.validation.marker; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.IFileEditorInput; +import org.eclipse.ui.IMarkerResolution; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.ide.IDE; +import org.eclipse.ui.part.FileEditorInput; +import org.eclipse.ui.texteditor.AbstractTextEditor; + +public abstract class AbstractMarkerResolution implements IMarkerResolution { + + @Override + public final void run(IMarker marker) { + // Se if there is an open editor on the file containing the marker + IWorkbenchWindow w = PlatformUI.getWorkbench().getWorkbenchWindows()[0]; + // .getActiveWorkbenchWindow(); + if (w == null) + return; + IWorkbenchPage page = w.getActivePage(); + if (page == null) + return; + IFileEditorInput input = new FileEditorInput((IFile) marker.getResource()); + IEditorPart editorPart = page.findEditor(input); + + if (editorPart == null) { + // open an editor + try { + editorPart = IDE.openEditor(page, (IFile) marker.getResource(), true); + } catch (PartInitException e) { + // MessageDialog.openError(w.getShell(), MessageUtil + // .getString("Resolution_Error"), //$NON-NLS-1$ + // MessageUtil.getString("Unable_to_open_file_editor")); //$NON-NLS-1$ + } + } + if (editorPart == null || !(editorPart instanceof AbstractTextEditor)) + return; + // insert the sentence + AbstractTextEditor editor = (AbstractTextEditor) editorPart; + IDocument doc = editor.getDocumentProvider().getDocument(editor.getEditorInput()); + if (!run(marker, editor, doc)) { + return; + } + // delete the marker + try { + marker.delete(); + } catch (CoreException e) { + e.printStackTrace(); + // ignore + } + } + + protected abstract boolean run(IMarker marker, AbstractTextEditor editor, IDocument doc); +} \ No newline at end of file diff --git a/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/validation/marker/EditorConfigMarkerResolution.java b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/validation/marker/EditorConfigMarkerResolution.java new file mode 100644 index 000000000..5bfce0d96 --- /dev/null +++ b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/validation/marker/EditorConfigMarkerResolution.java @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2017 Angelo ZERR. + * 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 + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Angelo Zerr - initial API and implementation + */ +package org.eclipse.editorconfig.internal.validation.marker; + +import java.util.ArrayList; +import java.util.List; + +import org.ec4j.core.model.PropertyType; +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.ui.IMarkerResolution; +import org.eclipse.ui.IMarkerResolutionGenerator2; + +public class EditorConfigMarkerResolution implements IMarkerResolutionGenerator2 { + + @Override + public IMarkerResolution[] getResolutions(IMarker marker) { + List res = new ArrayList<>(1); + try { + if (MarkerUtils.isOptionType(marker, PropertyType.insert_final_newline.getName())) { + res.add(InsertFinalNewLineMarkerResolution.INSTANCE); + } else if (MarkerUtils.isOptionType(marker, PropertyType.trim_trailing_whitespace.getName())) { + res.add(TrimTrailingWhitespaceMarkerResolution.INSTANCE); + } + } catch (CoreException e) { + e.printStackTrace(); + } + return res.toArray(new IMarkerResolution[res.size()]); + } + + @Override + public boolean hasResolutions(IMarker marker) { + return MarkerUtils.isEditorConfigMarker(marker); + } + +} diff --git a/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/validation/marker/InsertFinalNewLineMarkerResolution.java b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/validation/marker/InsertFinalNewLineMarkerResolution.java new file mode 100644 index 000000000..18f446eef --- /dev/null +++ b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/validation/marker/InsertFinalNewLineMarkerResolution.java @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2017 Angelo ZERR. + * 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 + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Angelo Zerr - initial API and implementation + */ +package org.eclipse.editorconfig.internal.validation.marker; + +import org.eclipse.core.resources.IMarker; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IDocumentExtension4; +import org.eclipse.swt.custom.StyledText; +import org.eclipse.swt.widgets.Control; +import org.eclipse.text.edits.ReplaceEdit; +import org.eclipse.ui.IMarkerResolution; +import org.eclipse.ui.texteditor.AbstractTextEditor; + +final class InsertFinalNewLineMarkerResolution extends AbstractMarkerResolution { + + public static final IMarkerResolution INSTANCE = new InsertFinalNewLineMarkerResolution(); + + @Override + public String getLabel() { + return "Fix insert final new line"; + } + + @Override + protected boolean run(IMarker marker, AbstractTextEditor editor, IDocument doc) { + String delimiter = ((IDocumentExtension4) doc).getDefaultLineDelimiter(); + int offset = marker.getAttribute(IMarker.CHAR_END, -1); + StyledText text = (StyledText) editor.getAdapter(Control.class); + text.getDisplay().syncExec(() -> { + MarkerUtils.applyEdits(doc, new ReplaceEdit(offset, 0, delimiter)); + }); + return true; + } + +} \ No newline at end of file diff --git a/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/validation/marker/MarkerUtils.java b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/validation/marker/MarkerUtils.java new file mode 100644 index 000000000..abfd4c663 --- /dev/null +++ b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/validation/marker/MarkerUtils.java @@ -0,0 +1,102 @@ +/** + * Copyright (c) 2017 Angelo ZERR. + * 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 + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Angelo Zerr - initial API and implementation + */ +package org.eclipse.editorconfig.internal.validation.marker; + +import java.util.Arrays; +import java.util.List; + +import org.ec4j.core.model.PropertyType; +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.editorconfig.EditorConfigPlugin; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.RewriteSessionEditProcessor; +import org.eclipse.text.edits.MalformedTreeException; +import org.eclipse.text.edits.TextEdit; +import org.eclipse.text.undo.DocumentUndoManagerRegistry; +import org.eclipse.text.undo.IDocumentUndoManager; + +/** + * EditorConfig marker utilities. + * + * @author azerr + * + */ +public class MarkerUtils { + + private static final String EC_ATTRIBUTE_OPTION_TYPE = "ecOptionType"; //$NON-NLS-1$ + private static final String EC_PROBLEM_MARKER_TYPE = "org.eclipse.editorconfig.problem"; //$NON-NLS-1$ + + public static PropertyType getOptionType(IMarker marker) throws CoreException { + return (PropertyType) marker.getAttribute(EC_ATTRIBUTE_OPTION_TYPE); + } + + public static boolean isOptionType(IMarker marker, String name) throws CoreException { + PropertyType type = getOptionType(marker); + return type != null && type.getName().equals(name); + } + + public static void setOptionType(IMarker marker, PropertyType type) throws CoreException { + marker.setAttribute(EC_ATTRIBUTE_OPTION_TYPE, type); + } + + public static boolean isEditorConfigMarker(IMarker marker) { + try { + return EC_PROBLEM_MARKER_TYPE.equals(marker.getType()); + } catch (CoreException e) { + return false; + } + } + + public static List findEditorConfigMarkers(IResource resource) throws CoreException { + return Arrays.asList(resource.findMarkers(EC_PROBLEM_MARKER_TYPE, false, IResource.DEPTH_ONE)); + } + + public static IMarker createEditorConfigMarker(IResource resource) throws CoreException { + return resource.createMarker(EC_PROBLEM_MARKER_TYPE); + } + + public static int getSeverity(org.ec4j.core.parser.ErrorEvent.ErrorType errorType) { + return errorType.isSyntaxError() ? IMarker.SEVERITY_ERROR : IMarker.SEVERITY_WARNING; + } + + /** + * Method will apply all edits to document as single modification. Needs to + * be executed in UI thread. + * + * @param document + * document to modify + * @param edits + * list of LSP TextEdits + */ + public static void applyEdits(IDocument document, TextEdit edit) { + if (document == null) { + return; + } + + IDocumentUndoManager manager = DocumentUndoManagerRegistry.getDocumentUndoManager(document); + if (manager != null) { + manager.beginCompoundChange(); + } + try { + RewriteSessionEditProcessor editProcessor = new RewriteSessionEditProcessor(document, edit, + org.eclipse.text.edits.TextEdit.NONE); + editProcessor.performEdits(); + } catch (MalformedTreeException | BadLocationException e) { + EditorConfigPlugin.logError(e); + } + if (manager != null) { + manager.endCompoundChange(); + } + } +} diff --git a/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/validation/marker/TrimTrailingWhitespaceMarkerResolution.java b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/validation/marker/TrimTrailingWhitespaceMarkerResolution.java new file mode 100644 index 000000000..d9bec7ccb --- /dev/null +++ b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/validation/marker/TrimTrailingWhitespaceMarkerResolution.java @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2017 Angelo ZERR. + * 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 + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Angelo Zerr - initial API and implementation + */ +package org.eclipse.editorconfig.internal.validation.marker; + +import org.eclipse.core.resources.IMarker; +import org.eclipse.jface.text.IDocument; +import org.eclipse.swt.custom.StyledText; +import org.eclipse.swt.widgets.Control; +import org.eclipse.text.edits.ReplaceEdit; +import org.eclipse.ui.IMarkerResolution; +import org.eclipse.ui.texteditor.AbstractTextEditor; + +final class TrimTrailingWhitespaceMarkerResolution extends AbstractMarkerResolution { + + public static final IMarkerResolution INSTANCE = new TrimTrailingWhitespaceMarkerResolution(); + + @Override + public String getLabel() { + return "Fix trim trailing whitespace"; + } + + @Override + protected boolean run(IMarker marker, AbstractTextEditor editor, IDocument doc) { + int start = marker.getAttribute(IMarker.CHAR_START, -1); + int end = marker.getAttribute(IMarker.CHAR_END, -1); + StyledText text = (StyledText) editor.getAdapter(Control.class); + text.getDisplay().syncExec(() -> { + MarkerUtils.applyEdits(doc, new ReplaceEdit(start, end - start, "")); + }); + return true; + } + +} \ No newline at end of file diff --git a/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/wizards/NewEditorConfigFileWizardPage.java b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/wizards/NewEditorConfigFileWizardPage.java new file mode 100644 index 000000000..d8390b81a --- /dev/null +++ b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/wizards/NewEditorConfigFileWizardPage.java @@ -0,0 +1,162 @@ +/** + * Copyright (c) 2017 Angelo ZERR. + * 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 + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Angelo Zerr - initial API and implementation + */ +package org.eclipse.editorconfig.internal.wizards; + +import org.ec4j.core.EditorConfigConstants; +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.Path; +import org.eclipse.editorconfig.internal.EditorConfigMessages; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.wizard.WizardPage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.dialogs.ContainerSelectionDialog; + +/** + * Wizard page to fill information of .editorconfig file to create. + * + */ +public class NewEditorConfigFileWizardPage extends WizardPage { + + private static final String PAGE_NAME = NewEditorConfigFileWizardPage.class.getName(); + + private static final IPath EDITOTR_CONFIG_PATH = new Path(EditorConfigConstants.EDITORCONFIG); + + private Text folderText; + + private ISelection selection; + + /** + * Constructor for NewEditorConfigFileWizardPage. + * + * @param pageName + */ + public NewEditorConfigFileWizardPage(ISelection selection) { + super(PAGE_NAME); + setTitle(EditorConfigMessages.NewEditorConfigFileWizardPage_title); + setDescription(EditorConfigMessages.NewEditorConfigFileWizardPage_description); + this.selection = selection; + } + + @Override + public void createControl(Composite parent) { + Composite container = new Composite(parent, SWT.NULL); + GridLayout layout = new GridLayout(); + container.setLayout(layout); + layout.numColumns = 3; + layout.verticalSpacing = 9; + Label label = new Label(container, SWT.NULL); + label.setText(EditorConfigMessages.NewEditorConfigFileWizardPage_folderText_Label); + + folderText = new Text(container, SWT.BORDER | SWT.SINGLE); + GridData gd = new GridData(GridData.FILL_HORIZONTAL); + folderText.setLayoutData(gd); + folderText.addModifyListener(new ModifyListener() { + public void modifyText(ModifyEvent e) { + dialogChanged(); + } + }); + + Button button = new Button(container, SWT.PUSH); + button.setText(EditorConfigMessages.Browse_button); + button.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + handleBrowse(); + } + }); + initialize(); + dialogChanged(); + setControl(container); + } + + /** + * Tests if the current workbench selection is a suitable container to use. + */ + private void initialize() { + if (selection != null && selection.isEmpty() == false && selection instanceof IStructuredSelection) { + IStructuredSelection ssel = (IStructuredSelection) selection; + if (ssel.size() > 1) + return; + Object obj = ssel.getFirstElement(); + if (obj instanceof IResource) { + IContainer container; + if (obj instanceof IContainer) + container = (IContainer) obj; + else + container = ((IResource) obj).getParent(); + folderText.setText(container.getFullPath().toString()); + } + } + } + + /** + * Uses the standard container selection dialog to choose the new value for the + * container field. + */ + private void handleBrowse() { + ContainerSelectionDialog dialog = new ContainerSelectionDialog(getShell(), + ResourcesPlugin.getWorkspace().getRoot(), false, + EditorConfigMessages.NewEditorConfigFileWizardPage_containerSelectionDialog_title); + if (dialog.open() == ContainerSelectionDialog.OK) { + Object[] result = dialog.getResult(); + if (result.length == 1) { + folderText.setText(((Path) result[0]).toString()); + } + } + } + + /** + * Ensures that both text fields are set. + */ + private void dialogChanged() { + IResource container = ResourcesPlugin.getWorkspace().getRoot().findMember(new Path(getContainerName())); + if (getContainerName().length() == 0) { + updateStatus(EditorConfigMessages.NewEditorConfigFileWizardPage_folder_required_error); + return; + } + if (container == null || (container.getType() & (IResource.PROJECT | IResource.FOLDER)) == 0) { + updateStatus(EditorConfigMessages.NewEditorConfigFileWizardPage_folder_noexists_error); + return; + } + if (!container.isAccessible()) { + updateStatus(EditorConfigMessages.NewEditorConfigFileWizardPage_project_noaccessible_error); + return; + } + if (((IContainer) container).exists(EDITOTR_CONFIG_PATH)) { + updateStatus(EditorConfigMessages.NewEditorConfigFileWizardPage_folder_already_editorconfig_error); + return; + } + updateStatus(null); + } + + private void updateStatus(String message) { + setErrorMessage(message); + setPageComplete(message == null); + } + + public String getContainerName() { + return folderText.getText(); + } + +} \ No newline at end of file diff --git a/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/wizards/NewEditorConfigWizard.java b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/wizards/NewEditorConfigWizard.java new file mode 100644 index 000000000..1b5f314bf --- /dev/null +++ b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/internal/wizards/NewEditorConfigWizard.java @@ -0,0 +1,192 @@ +/** + * Copyright (c) 2017 Angelo ZERR. + * 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 + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Angelo Zerr - initial API and implementation + */ +package org.eclipse.editorconfig.internal.wizards; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; + +import org.ec4j.core.EditorConfigConstants; +import org.ec4j.core.ide.IdeSupportService; +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IWorkspaceRoot; +import org.eclipse.core.resources.ProjectScope; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Path; +import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.preferences.IScopeContext; +import org.eclipse.core.runtime.preferences.InstanceScope; +import org.eclipse.editorconfig.EditorConfigPlugin; +import org.eclipse.editorconfig.internal.EditorConfigMessages; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.wizard.Wizard; +import org.eclipse.ui.INewWizard; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.editors.text.EditorsUI; +import org.eclipse.ui.ide.IDE; +import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants; + +/** + * Wizard to create an .editorconfig file by using Eclipse preferences. + */ + +public class NewEditorConfigWizard extends Wizard implements INewWizard { + + private NewEditorConfigFileWizardPage page; + private ISelection selection; + + @Override + public void addPages() { + page = new NewEditorConfigFileWizardPage(selection); + addPage(page); + } + + @Override + public void init(IWorkbench workbench, IStructuredSelection currentSelection) { + setWindowTitle(EditorConfigMessages.NewEditorConfigWizard_windowTitle); + setNeedsProgressMonitor(true); + this.selection = currentSelection; + } + + @Override + public boolean performFinish() { + final String containerName = page.getContainerName(); + final String fileName = EditorConfigConstants.EDITORCONFIG; + try { + getContainer().run(true, false, (monitor) -> { + try { + doFinish(containerName, fileName, monitor); + } catch (CoreException e) { + throw new InvocationTargetException(e); + } finally { + monitor.done(); + } + }); + } catch (InterruptedException e) { + return false; + } catch (InvocationTargetException e) { + Throwable realException = e.getTargetException(); + MessageDialog.openError(getShell(), "Error", realException.getMessage()); + return false; + } + return true; + } + + /** + * The worker method. It will find the container, create the file if missing or + * just replace its contents, and open the editor on the newly created file. + */ + + private void doFinish(String containerName, String fileName, IProgressMonitor monitor) throws CoreException { + // create a sample file + monitor.beginTask("Creating " + fileName, 2); + IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); + IResource resource = root.findMember(new Path(containerName)); + if (!resource.exists() || !(resource instanceof IContainer)) { + throwCoreException("Container \"" + containerName + "\" does not exist."); + } + IContainer container = (IContainer) resource; + final IFile file = container.getFile(new Path(fileName)); + try (InputStream stream = openContentStream(container);) { + file.create(stream, false, monitor); + } catch (IOException e) { + } + monitor.worked(1); + monitor.setTaskName("Opening file for editing..."); + getShell().getDisplay().asyncExec(() -> { + IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(); + try { + IDE.openEditor(page, file, true); + } catch (PartInitException e) { + } + }); + monitor.worked(1); + } + + /** + * Returns the content of the .editorconfig file to generate. + * + * @param container + * + * @return the content of the .editorconfig file to generate. + */ + private InputStream openContentStream(IContainer container) { + IPreferenceStore store = EditorsUI.getPreferenceStore(); + boolean spacesForTabs = store.getBoolean(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SPACES_FOR_TABS); + int tabWidth = store.getInt(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_TAB_WIDTH); + String lineDelimiter = getLineDelimiter(container); + String endOfLine = org.ec4j.core.model.PropertyType.EndOfLineValue.ofEndOfLineString(lineDelimiter).name(); + + StringBuilder content = new StringBuilder("# EditorConfig is awesome: http://EditorConfig.org"); + content.append(lineDelimiter); + content.append(lineDelimiter); + content.append("[*]"); + content.append(lineDelimiter); + content.append("indent_style = "); + content.append(spacesForTabs ? "space" : "tab"); + content.append(lineDelimiter); + content.append("indent_size = "); + content.append(tabWidth); + if (endOfLine != null) { + content.append(lineDelimiter); + content.append("end_of_line = "); + content.append(endOfLine); + } + + return new ByteArrayInputStream(content.toString().getBytes()); + } + + private void throwCoreException(String message) throws CoreException { + IStatus status = new Status(IStatus.ERROR, EditorConfigPlugin.PLUGIN_ID, IStatus.OK, message, null); + throw new CoreException(status); + } + + private static String getLineDelimiter(IContainer file) { + String lineDelimiter = getLineDelimiterPreference(file); + if (lineDelimiter == null) { + lineDelimiter = System.getProperty("line.separator"); + } + if (lineDelimiter != null) { + return lineDelimiter; + } + return "\n"; + } + + private static String getLineDelimiterPreference(IContainer file) { + IScopeContext[] scopeContext; + if (file != null && file.getProject() != null) { + // project preference + scopeContext = new IScopeContext[] { new ProjectScope(file.getProject()) }; + String lineDelimiter = Platform.getPreferencesService().getString(Platform.PI_RUNTIME, + Platform.PREF_LINE_SEPARATOR, null, scopeContext); + if (lineDelimiter != null) + return lineDelimiter; + } + // workspace preference + scopeContext = new IScopeContext[] { InstanceScope.INSTANCE }; + return Platform.getPreferencesService().getString(Platform.PI_RUNTIME, Platform.PREF_LINE_SEPARATOR, null, + scopeContext); + } + +} \ No newline at end of file diff --git a/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/search/AbstractSectionPatternVisitor.java b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/search/AbstractSectionPatternVisitor.java new file mode 100644 index 000000000..c76aa4651 --- /dev/null +++ b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/search/AbstractSectionPatternVisitor.java @@ -0,0 +1,30 @@ +package org.eclipse.editorconfig.search; + +import org.ec4j.core.model.Section; +import org.ec4j.core.model.Ec4jPath.Ec4jPaths; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IResourceProxy; +import org.eclipse.core.resources.IResourceProxyVisitor; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; + +public abstract class AbstractSectionPatternVisitor implements IResourceProxyVisitor { + + private final Section section; + + public AbstractSectionPatternVisitor(Section section) { + this.section = section; + } + + @Override + public boolean visit(IResourceProxy proxy) throws CoreException { + IPath path = proxy.requestFullPath(); + if (proxy.getType() == IResource.FILE && section.match(Ec4jPaths.of(path.toString()))) { + collect(proxy); + } + return true; + } + + protected abstract void collect(IResourceProxy proxy); + +} diff --git a/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/search/CountSectionPatternVisitor.java b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/search/CountSectionPatternVisitor.java new file mode 100644 index 000000000..1c1195c42 --- /dev/null +++ b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/search/CountSectionPatternVisitor.java @@ -0,0 +1,23 @@ +package org.eclipse.editorconfig.search; + +import org.ec4j.core.model.Section; +import org.eclipse.core.resources.IResourceProxy; + +public class CountSectionPatternVisitor extends AbstractSectionPatternVisitor { + + private int nbFiles; + + public CountSectionPatternVisitor(Section section) { + super(section); + } + + @Override + protected void collect(IResourceProxy proxy) { + nbFiles++; + } + + public int getNbFiles() { + return nbFiles; + } + +} diff --git a/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/search/EditorConfigSearchQuery.java b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/search/EditorConfigSearchQuery.java new file mode 100644 index 000000000..a342bc1ed --- /dev/null +++ b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/search/EditorConfigSearchQuery.java @@ -0,0 +1,102 @@ +package org.eclipse.editorconfig.search; + +import org.ec4j.core.model.Section; +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IResourceProxy; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.Status; +import org.eclipse.editorconfig.EditorConfigPlugin; +import org.eclipse.editorconfig.internal.EditorConfigMessages; +import org.eclipse.osgi.util.NLS; +import org.eclipse.search.internal.ui.text.FileMatch; +import org.eclipse.search.internal.ui.text.FileSearchQuery; +import org.eclipse.search.ui.ISearchQuery; +import org.eclipse.search.ui.ISearchResult; +import org.eclipse.search.ui.text.AbstractTextSearchResult; +import org.eclipse.search.ui.text.Match; + +/** + * {@link ISearchQuery} implementation for EditorConfig section matching files. + * + */ +public class EditorConfigSearchQuery extends FileSearchQuery { + + private final Section section; + private final IFile configFile; + + private EditorConfigSearchResult result; + private long startTime; + + /** + * EditorConfig section matching files query to "Find matching files" from the + * given section + * + * @param section + * @param configFile + */ + public EditorConfigSearchQuery(Section section, IFile configFile) { + super("", false, false, null); //$NON-NLS-1$ + this.section = section; + this.configFile = configFile; + } + + @Override + public IStatus run(IProgressMonitor monitor) throws OperationCanceledException { + startTime = System.currentTimeMillis(); + AbstractTextSearchResult textResult = (AbstractTextSearchResult) getSearchResult(); + textResult.removeAll(); + + try { + + IContainer dir = configFile.getParent(); + dir.accept(new AbstractSectionPatternVisitor(section) { + + @Override + protected void collect(IResourceProxy proxy) { + Match match = new FileMatch((IFile) proxy.requestResource()); + result.addMatch(match); + } + }, IResource.NONE); + + return Status.OK_STATUS; + } catch (Exception ex) { + return new Status(IStatus.ERROR, EditorConfigPlugin.PLUGIN_ID, ex.getMessage(), ex); + } + } + + @Override + public ISearchResult getSearchResult() { + if (result == null) { + result = new EditorConfigSearchResult(this); + } + return result; + } + + @Override + public String getLabel() { + return EditorConfigMessages.EditorConfigSearchQuery_label; + } + + @Override + public String getResultLabel(int nMatches) { + long time = 0; + if (startTime > 0) { + time = System.currentTimeMillis() - startTime; + } + if (nMatches == 1) { + return NLS.bind(EditorConfigMessages.EditorConfigSearchQuery_singularReference, + new Object[] { section.getGlob(), time }); + } + return NLS.bind(EditorConfigMessages.EditorConfigSearchQuery_pluralReferences, + new Object[] { section.getGlob(), nMatches, time }); + } + + @Override + public boolean isFileNameSearch() { + return true; + } +} \ No newline at end of file diff --git a/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/search/EditorConfigSearchResult.java b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/search/EditorConfigSearchResult.java new file mode 100644 index 000000000..d80233184 --- /dev/null +++ b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/search/EditorConfigSearchResult.java @@ -0,0 +1,12 @@ + +package org.eclipse.editorconfig.search; + +import org.eclipse.search.internal.ui.text.FileSearchResult; + +public class EditorConfigSearchResult extends FileSearchResult { + + public EditorConfigSearchResult(EditorConfigSearchQuery job) { + super(job); + } + +} diff --git a/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/utils/EditorUtils.java b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/utils/EditorUtils.java new file mode 100644 index 000000000..a39013f51 --- /dev/null +++ b/org.eclipse.editorconfig/src/main/java/org/eclipse/editorconfig/utils/EditorUtils.java @@ -0,0 +1,27 @@ +package org.eclipse.editorconfig.utils; + +import org.ec4j.core.EditorConfigConstants; +import org.eclipse.core.resources.IFile; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IFileEditorInput; +import org.eclipse.ui.texteditor.ITextEditor; + +public class EditorUtils { + + public static boolean isEditorConfigFile(ITextEditor textEditor) { + IFile configFile = EditorUtils.getFile(textEditor); + return isEditorConfigFile(configFile); + } + + public static boolean isEditorConfigFile(IFile configFile) { + return configFile != null && EditorConfigConstants.EDITORCONFIG.equals(configFile.getName()); + } + + public static IFile getFile(ITextEditor textEditor) { + IEditorInput input = textEditor.getEditorInput(); + if (input instanceof IFileEditorInput) { + return ((IFileEditorInput) input).getFile(); + } + return null; + } +} diff --git a/org.eclipse.editorconfig/src/main/resources/.gitkeep b/org.eclipse.editorconfig/src/main/resources/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/org.eclipse.editorconfig/src/test/resources/.gitkeep b/org.eclipse.editorconfig/src/test/resources/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/org.eclipse.ui.genericeditor/META-INF/MANIFEST.MF b/org.eclipse.ui.genericeditor/META-INF/MANIFEST.MF index aeaf84dd6..660fbbcf9 100644 --- a/org.eclipse.ui.genericeditor/META-INF/MANIFEST.MF +++ b/org.eclipse.ui.genericeditor/META-INF/MANIFEST.MF @@ -16,11 +16,12 @@ Require-Bundle: org.eclipse.ui.workbench.texteditor;bundle-version="3.10.0", org.eclipse.core.resources;bundle-version="3.11.0", org.eclipse.core.expressions;bundle-version="3.6.0", org.eclipse.compare;resolution:=optional -Export-Package: org.eclipse.ui.internal.genericeditor;x-internal:=true, +Export-Package: org.eclipse.ui.genericeditor, + org.eclipse.ui.internal.genericeditor;x-internal:=true, + org.eclipse.ui.internal.genericeditor.compare;x-internal:=true, org.eclipse.ui.internal.genericeditor.hover;x-internal:=true, org.eclipse.ui.internal.genericeditor.markers;x-internal:=true, - org.eclipse.ui.internal.genericeditor.preferences;x-internal:=true, - org.eclipse.ui.internal.genericeditor.compare;x-internal:=true + org.eclipse.ui.internal.genericeditor.preferences;x-internal:=true Bundle-Activator: org.eclipse.ui.internal.genericeditor.GenericEditorPlugin Bundle-Localization: plugin Bundle-ActivationPolicy: lazy diff --git a/org.eclipse.ui.genericeditor/plugin.xml b/org.eclipse.ui.genericeditor/plugin.xml index 9a8755e5f..fac1f2291 100644 --- a/org.eclipse.ui.genericeditor/plugin.xml +++ b/org.eclipse.ui.genericeditor/plugin.xml @@ -26,6 +26,7 @@ + + + + + + + + + This extension point is used to contribute reconcilers for controlling the presentation on a file with a given content type. + + + + + + + + + + + + + + + + + + + a fully qualified identifier of the target extension point + + + + + + + an optional identifier of the extension instance + + + + + + + an optional name of the extension instance + + + + + + + + + + + + + + + + + + The fully qualified class name implementing the interface <code>org.eclipse.jface.text.reconciler.IReconciler</code> + + + + + + + + + + The target content-type for this extension. Content-types are defined as extension to the org.eclipse.core.contenttype.contentTypes extension point. + + + + + + + + + + + + + A core Expression that controls the enabled of the given reconciler. The viewer, editor, and editor input are registered in the evaluation context as variable: + + * <with variable="viewer"/> : use it if your expression requires the viewer. + * <with variable="document"/> : use it if your expression requires the document. + * <with variable="editor"/> : use it if your expression requires the editor (deprecated, not always set). + * <with variable="editorInput"/> : use it if your expression requires the editor input (deprecated, not always set). + + + + + + + + + + + + + + + + + + + + + + + + + + + 1.1 + + + + + + + + + Below is an example of how to use the Reconciler extension point: + +<pre> +<extension point="org.eclipse.ui.genericeditor.reconcilers"> + <reconciler + class="org.eclipse.ui.genericeditor.examples.TargetDefinitionReconciler" + contentType="org.eclipse.pde.targetFile"> + <enabledWhen> + <with variable="editor"> + <test property="org.eclipse.ui.genericeditor.examples.TargetDefinitionPropertyTester"> + </test> + </with> + </enabledWhen> + </reconciler> +</extension> +</pre> + + + + + + + + + + + Copyright (c) 2017 Red Hat Inc. and others + +This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 which accompanies this distribution, and is available at <a href="https://www.eclipse.org/legal/epl-2.0">https://www.eclipse.org/legal/epl-v20.html</a>/ + +SPDX-License-Identifier: EPL-2.0 + + + + diff --git a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/genericeditor/IPreferenceStoreProvider.java b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/genericeditor/IPreferenceStoreProvider.java new file mode 100644 index 000000000..50a99b346 --- /dev/null +++ b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/genericeditor/IPreferenceStoreProvider.java @@ -0,0 +1,10 @@ +package org.eclipse.ui.genericeditor; + +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.text.source.ISourceViewer; +import org.eclipse.ui.texteditor.ITextEditor; + +public interface IPreferenceStoreProvider { + + IPreferenceStore getPreferenceStore(ISourceViewer viewer, ITextEditor editor); +} diff --git a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/ExtensionBasedTextEditor.java b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/ExtensionBasedTextEditor.java index 5cea4e45a..e692d2305 100644 --- a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/ExtensionBasedTextEditor.java +++ b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/ExtensionBasedTextEditor.java @@ -16,14 +16,25 @@ *******************************************************************************/ package org.eclipse.ui.internal.genericeditor; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.LinkedList; import java.util.List; +import java.util.Queue; +import java.util.Set; +import org.eclipse.core.filebuffers.FileBuffers; +import org.eclipse.core.filebuffers.ITextFileBuffer; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.content.IContentType; import org.eclipse.jface.dialogs.MessageDialog; -import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITextSelection; import org.eclipse.jface.text.Region; @@ -39,10 +50,9 @@ import org.eclipse.swt.graphics.Image; import org.eclipse.swt.widgets.Composite; import org.eclipse.ui.IEditorInput; -import org.eclipse.ui.editors.text.EditorsUI; import org.eclipse.ui.editors.text.TextEditor; import org.eclipse.ui.internal.genericeditor.preferences.GenericEditorPreferenceConstants; -import org.eclipse.ui.texteditor.ChainedPreferenceStore; +import org.eclipse.ui.texteditor.ITextEditor; import org.eclipse.ui.texteditor.SourceViewerDecorationSupport; /** @@ -65,6 +75,8 @@ public class ExtensionBasedTextEditor extends TextEditor { private Image contentTypeImage; private ImageDescriptor contentTypeImageDescripter; + private Set resolvedContentTypes; + /** * */ @@ -174,8 +186,7 @@ public void createPartControl(Composite parent) { @Override protected void initializeEditor() { super.initializeEditor(); - setPreferenceStore(new ChainedPreferenceStore(new IPreferenceStore[] { - GenericEditorPreferenceConstants.getPreferenceStore(), EditorsUI.getPreferenceStore() })); + setPreferenceStore(new PreferenceStoreWrapper(this)); } /** @@ -188,7 +199,7 @@ protected void initializeEditor() { */ private void configureCharacterPairMatcher(ISourceViewer viewer, SourceViewerDecorationSupport support) { List matchers = GenericEditorPlugin.getDefault().getCharacterPairMatcherRegistry() - .getCharacterPairMatchers(viewer, this, configuration.getContentTypes(viewer.getDocument())); + .getCharacterPairMatchers(viewer, this, getContentTypes(viewer.getDocument())); if (!matchers.isEmpty()) { ICharacterPairMatcher matcher = matchers.get(0); support.setCharacterPairMatcher(matcher); @@ -203,19 +214,78 @@ public Image getTitleImage() { } private void computeImage() { - contentTypeImageDescripter = GenericEditorPlugin.getDefault().getContentTypeImagesRegistry() - .getImageDescriptor(getContentTypes()); + Set contentTypes = getContentTypes(); + contentTypeImageDescripter = GenericEditorPlugin.getDefault().getContentTypeImagesRegistry().getImageDescriptor( + contentTypes != null ? contentTypes.toArray(new IContentType[] {}) : new IContentType[0]); if (contentTypeImageDescripter != null) { this.contentTypeImage = contentTypeImageDescripter.createImage(); } } - private IContentType[] getContentTypes() { + Set getContentTypes() { ISourceViewer sourceViewer = getSourceViewer(); if (sourceViewer != null) { - return configuration.getContentTypes(sourceViewer.getDocument()).toArray(new IContentType[] {}); + return getContentTypes(sourceViewer.getDocument()); + } + return null; + } + + public Set getContentTypes(IDocument document) { + if (this.resolvedContentTypes != null) { + return this.resolvedContentTypes; + } + return this.resolvedContentTypes = collectContentTypes(document, this); + } + + public static Set collectContentTypes(IDocument document, ITextEditor editor) { + Set resolvedContentTypes = new LinkedHashSet<>(); + ITextFileBuffer buffer = getCurrentBuffer(document); + if (buffer != null) { + try { + IContentType contentType = buffer.getContentType(); + if (contentType != null) { + resolvedContentTypes.add(contentType); + } + } catch (CoreException ex) { + GenericEditorPlugin.getDefault().getLog() + .log(new Status(IStatus.ERROR, GenericEditorPlugin.BUNDLE_ID, ex.getMessage(), ex)); + } + } + String fileName = getCurrentFileName(document, editor); + if (fileName != null) { + Queue types = new LinkedList<>( + Arrays.asList(Platform.getContentTypeManager().findContentTypesFor(fileName))); + while (!types.isEmpty()) { + IContentType type = types.poll(); + resolvedContentTypes.add(type); + IContentType parent = type.getBaseType(); + if (parent != null) { + types.add(parent); + } + } } - return new IContentType[] {}; + return resolvedContentTypes; + } + + private static ITextFileBuffer getCurrentBuffer(IDocument document) { + if (document != null) { + return FileBuffers.getTextFileBufferManager().getTextFileBuffer(document); + } + return null; + } + + private static String getCurrentFileName(IDocument document, ITextEditor editor) { + String fileName = editor != null ? editor.getEditorInput().getName() : null; + if (fileName == null) { + ITextFileBuffer buffer = getCurrentBuffer(document); + if (buffer != null) { + IPath path = buffer.getLocation(); + if (path != null) { + fileName = path.lastSegment(); + } + } + } + return fileName; } @Override @@ -226,4 +296,9 @@ public void dispose() { } super.dispose(); } + + ISourceViewer getViewer() { + return getSourceViewer(); + } + } diff --git a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/ExtensionBasedTextViewerConfiguration.java b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/ExtensionBasedTextViewerConfiguration.java index 2c14aa78c..51e7c8ed4 100644 --- a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/ExtensionBasedTextViewerConfiguration.java +++ b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/ExtensionBasedTextViewerConfiguration.java @@ -19,25 +19,15 @@ package org.eclipse.ui.internal.genericeditor; import java.util.ArrayList; -import java.util.Arrays; +import java.util.Collections; import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Queue; import java.util.Set; import java.util.stream.Collectors; -import org.eclipse.core.filebuffers.FileBuffers; -import org.eclipse.core.filebuffers.ITextFileBuffer; -import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IAdaptable; -import org.eclipse.core.runtime.IPath; -import org.eclipse.core.runtime.IStatus; -import org.eclipse.core.runtime.Platform; -import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.content.IContentType; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.text.AbstractReusableInformationControlCreator; @@ -73,7 +63,6 @@ import org.eclipse.ui.internal.genericeditor.hover.CompositeInformationControlCreator; import org.eclipse.ui.internal.genericeditor.hover.CompositeTextHover; import org.eclipse.ui.internal.genericeditor.markers.MarkerResoltionQuickAssistProcessor; -import org.eclipse.ui.texteditor.ITextEditor; /** * The configuration of the {@link ExtensionBasedTextEditor}. It registers the @@ -86,78 +75,26 @@ public final class ExtensionBasedTextViewerConfiguration extends TextSourceViewerConfiguration implements IDocumentPartitioningListener { - private ITextEditor editor; - private Set resolvedContentTypes; - private Set fallbackContentTypes = Set.of(); + private ExtensionBasedTextEditor editor; private IDocument document; private GenericEditorContentAssistant contentAssistant; + private Set fallbackContentTypes = Set.of(); /** * * @param editor the editor we're creating. * @param preferenceStore the preference store. */ - public ExtensionBasedTextViewerConfiguration(ITextEditor editor, IPreferenceStore preferenceStore) { + public ExtensionBasedTextViewerConfiguration(ExtensionBasedTextEditor editor, IPreferenceStore preferenceStore) { super(preferenceStore); this.editor = editor; } - public Set getContentTypes(IDocument document) { - if (this.resolvedContentTypes != null) { - return this.resolvedContentTypes; - } - this.resolvedContentTypes = new LinkedHashSet<>(); - ITextFileBuffer buffer = getCurrentBuffer(document); - if (buffer != null) { - try { - IContentType contentType = buffer.getContentType(); - if (contentType != null) { - this.resolvedContentTypes.add(contentType); - } - } catch (CoreException ex) { - GenericEditorPlugin.getDefault().getLog() - .log(new Status(IStatus.ERROR, GenericEditorPlugin.BUNDLE_ID, ex.getMessage(), ex)); - } - } - String fileName = getCurrentFileName(document); - if (fileName != null) { - Queue types = new LinkedList<>( - Arrays.asList(Platform.getContentTypeManager().findContentTypesFor(fileName))); - while (!types.isEmpty()) { - IContentType type = types.poll(); - this.resolvedContentTypes.add(type); - IContentType parent = type.getBaseType(); - if (parent != null) { - types.add(parent); - } - } - } - return this.resolvedContentTypes.isEmpty() ? fallbackContentTypes : resolvedContentTypes; - } - - private static ITextFileBuffer getCurrentBuffer(IDocument document) { - if (document != null) { - return FileBuffers.getTextFileBufferManager().getTextFileBuffer(document); - } - return null; - } - - private String getCurrentFileName(IDocument document) { - String fileName = null; - if (this.editor != null) { - fileName = editor.getEditorInput().getName(); - } - if (fileName == null) { - ITextFileBuffer buffer = getCurrentBuffer(document); - if (buffer != null) { - IPath path = buffer.getLocation(); - if (path != null) { - fileName = path.lastSegment(); - } - } - } - return fileName; + private Set getContentTypes(IDocument document) { + Set resolvedContentTypes = editor != null ? editor.getContentTypes(document) + : Collections.emptySet(); + return resolvedContentTypes.isEmpty() ? fallbackContentTypes : resolvedContentTypes; } @Override diff --git a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/GenericEditorContentAssistant.java b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/GenericEditorContentAssistant.java index fcd3797a3..9d597b8b0 100644 --- a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/GenericEditorContentAssistant.java +++ b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/GenericEditorContentAssistant.java @@ -74,7 +74,7 @@ public GenericEditorContentAssistant( setAutoActivationDelay(0); enableColoredLabels(true); enableAutoActivation(true); - enableAutoActivateCompletionOnType(true); + // enableAutoActivateCompletionOnType(true); setInformationControlCreator(new AbstractReusableInformationControlCreator() { @Override protected IInformationControl doCreateInformationControl(Shell parent) { diff --git a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/GenericEditorPlugin.java b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/GenericEditorPlugin.java index a3130c721..7bf7fa50d 100644 --- a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/GenericEditorPlugin.java +++ b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/GenericEditorPlugin.java @@ -49,6 +49,7 @@ public class GenericEditorPlugin extends AbstractUIPlugin { private AutoEditStrategyRegistry autoEditStrategyRegistry; private CharacterPairMatcherRegistry characterPairMatcherRegistry; private IconsRegistry editorImagesRegistry; + private PreferenceStoreProviderRegistry preferenceStoreRegistry; private IPropertyChangeListener themeListener; @@ -172,4 +173,11 @@ public synchronized IconsRegistry getContentTypeImagesRegistry() { } return this.editorImagesRegistry; } + + public synchronized PreferenceStoreProviderRegistry getPreferenceStoreRegistry() { + if (this.preferenceStoreRegistry == null) { + this.preferenceStoreRegistry = new PreferenceStoreProviderRegistry(); + } + return preferenceStoreRegistry; + } } diff --git a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/PreferenceStoreProviderRegistry.java b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/PreferenceStoreProviderRegistry.java new file mode 100644 index 000000000..d7a028a4e --- /dev/null +++ b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/PreferenceStoreProviderRegistry.java @@ -0,0 +1,97 @@ +/******************************************************************************* + * Copyright (c) 2016-2017 Red Hat Inc. and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Sopot Cela, Mickael Istria (Red Hat Inc.) - initial implementation + *******************************************************************************/ +package org.eclipse.ui.internal.genericeditor; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import org.eclipse.core.runtime.IConfigurationElement; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.content.IContentType; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.text.source.ISourceViewer; +import org.eclipse.ui.genericeditor.IPreferenceStoreProvider; +import org.eclipse.ui.texteditor.ITextEditor; + +/** + * A registry of {@link IPreferenceStore} provider provided by extension + * org.eclipse.ui.genericeditor.preferenceStoreProviders. Those + * extensions are specific to a given {@link IContentType}. + * + * @since 1.0 + */ +public class PreferenceStoreProviderRegistry { + + private static final String EXTENSION_POINT_ID = GenericEditorPlugin.BUNDLE_ID + ".preferenceStoreProviders"; //$NON-NLS-1$ + + private Map> extensions = new HashMap<>(); + private boolean outOfSync = true; + + /** + * Creates the registry and binds it to the extension point. + */ + public PreferenceStoreProviderRegistry() { + Platform.getExtensionRegistry().addRegistryChangeListener(event -> outOfSync = true, EXTENSION_POINT_ID); + } + + /** + * Get the contributed {@link IPresentationReconciliers}s that are relevant to + * hook on source viewer according to document content types. + * + * @param sourceViewer the source viewer we're hooking completion to. + * @param editor the text editor + * @param contentTypes the content types of the document we're editing. + * @return the list of {@link IPreferenceStore} contributed for at least one of + * the content types. + */ + public List getPreferenceStoreProviders(ISourceViewer sourceViewer, ITextEditor editor, + Set contentTypes) { + if (this.outOfSync) { + sync(); + } + return this.extensions.values().stream().filter(ext -> contentTypes.contains(ext.targetContentType)) + .filter(ext -> ext.matches(sourceViewer, editor)) + .sorted(new ContentTypeSpecializationComparator()) + .map(GenericContentTypeRelatedExtension::createDelegate) + .collect(Collectors.toList()); + } + + private void sync() { + Set toRemoveExtensions = new HashSet<>(this.extensions.keySet()); + for (IConfigurationElement extension : Platform.getExtensionRegistry() + .getConfigurationElementsFor(EXTENSION_POINT_ID)) { + toRemoveExtensions.remove(extension); + if (!this.extensions.containsKey(extension)) { + try { + this.extensions.put(extension, + new GenericContentTypeRelatedExtension(extension)); + } catch (Exception ex) { + GenericEditorPlugin.getDefault().getLog() + .log(new Status(IStatus.ERROR, GenericEditorPlugin.BUNDLE_ID, ex.getMessage(), ex)); + } + } + } + for (IConfigurationElement toRemove : toRemoveExtensions) { + this.extensions.remove(toRemove); + } + this.outOfSync = false; + } + +} diff --git a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/PreferenceStoreWrapper.java b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/PreferenceStoreWrapper.java new file mode 100644 index 000000000..5f9a13086 --- /dev/null +++ b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/PreferenceStoreWrapper.java @@ -0,0 +1,232 @@ +package org.eclipse.ui.internal.genericeditor; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import org.eclipse.core.runtime.content.IContentType; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.ui.editors.text.EditorsUI; +import org.eclipse.ui.genericeditor.IPreferenceStoreProvider; +import org.eclipse.ui.internal.genericeditor.preferences.GenericEditorPreferenceConstants; +import org.eclipse.ui.texteditor.ChainedPreferenceStore; + +public class PreferenceStoreWrapper implements IPreferenceStore { + + private IPreferenceStore delegate; + private boolean computed; + + private ExtensionBasedTextEditor editor; + + public PreferenceStoreWrapper(ExtensionBasedTextEditor editor) { + this.editor = editor; + this.delegate = createPreferenceStore(null, null); + } + + public static IPreferenceStore createPreferenceStore(Set contentTypes, + ExtensionBasedTextEditor editor) { + List stores = new ArrayList<>(); + if (contentTypes != null && editor != null) { + PreferenceStoreProviderRegistry registry = GenericEditorPlugin.getDefault().getPreferenceStoreRegistry(); + List providers = registry.getPreferenceStoreProviders(editor.getViewer(), editor, + contentTypes); + for (IPreferenceStoreProvider provider : providers) { + IPreferenceStore editorStore = provider.getPreferenceStore(editor.getViewer(), editor); + if (editorStore != null) { + stores.add(editorStore); + } + } + } + stores.add(GenericEditorPreferenceConstants.getPreferenceStore()); + stores.add(EditorsUI.getPreferenceStore()); + return new ChainedPreferenceStore(stores.toArray(new IPreferenceStore[stores.size()])); + } + + @Override + public void addPropertyChangeListener(IPropertyChangeListener arg0) { + getDelegate().addPropertyChangeListener(arg0); + } + + @Override + public boolean contains(String arg0) { + return getDelegate().contains(arg0); + } + + @Override + public void firePropertyChangeEvent(String arg0, Object arg1, Object arg2) { + getDelegate().firePropertyChangeEvent(arg0, arg1, arg2); + } + + @Override + public boolean getBoolean(String arg0) { + return getDelegate().getBoolean(arg0); + } + + @Override + public boolean getDefaultBoolean(String arg0) { + return getDelegate().getDefaultBoolean(arg0); + } + + @Override + public double getDefaultDouble(String arg0) { + return getDelegate().getDefaultDouble(arg0); + } + + @Override + public float getDefaultFloat(String arg0) { + return getDelegate().getDefaultFloat(arg0); + } + + @Override + public int getDefaultInt(String arg0) { + return getDelegate().getDefaultInt(arg0); + } + + @Override + public long getDefaultLong(String arg0) { + return getDelegate().getDefaultLong(arg0); + } + + @Override + public String getDefaultString(String arg0) { + return getDelegate().getDefaultString(arg0); + } + + @Override + public double getDouble(String arg0) { + return getDelegate().getDouble(arg0); + } + + @Override + public float getFloat(String arg0) { + return getDelegate().getFloat(arg0); + } + + @Override + public int getInt(String arg0) { + return getDelegate().getInt(arg0); + } + + @Override + public long getLong(String arg0) { + return getDelegate().getLong(arg0); + } + + @Override + public String getString(String arg0) { + return getDelegate().getString(arg0); + } + + @Override + public boolean isDefault(String arg0) { + return getDelegate().isDefault(arg0); + } + + @Override + public boolean needsSaving() { + return getDelegate().needsSaving(); + } + + @Override + public void putValue(String arg0, String arg1) { + getDelegate().putValue(arg0, arg1); + } + + @Override + public void removePropertyChangeListener(IPropertyChangeListener arg0) { + getDelegate().removePropertyChangeListener(arg0); + } + + @Override + public void setDefault(String arg0, boolean arg1) { + getDelegate().setDefault(arg0, arg1); + } + + @Override + public void setDefault(String arg0, double arg1) { + getDelegate().setDefault(arg0, arg1); + } + + @Override + public void setDefault(String arg0, float arg1) { + getDelegate().setDefault(arg0, arg1); + } + + @Override + public void setDefault(String arg0, int arg1) { + getDelegate().setDefault(arg0, arg1); + } + + @Override + public void setDefault(String arg0, long arg1) { + getDelegate().setDefault(arg0, arg1); + } + + @Override + public void setDefault(String arg0, String arg1) { + getDelegate().setDefault(arg0, arg1); + } + + @Override + public void setToDefault(String arg0) { + getDelegate().setToDefault(arg0); + } + + @Override + public void setValue(String arg0, boolean arg1) { + getDelegate().setValue(arg0, arg1); + } + + @Override + public void setValue(String arg0, double arg1) { + getDelegate().setValue(arg0, arg1); + } + + @Override + public void setValue(String arg0, float arg1) { + getDelegate().setValue(arg0, arg1); + } + + @Override + public void setValue(String arg0, int arg1) { + getDelegate().setValue(arg0, arg1); + } + + @Override + public void setValue(String arg0, long arg1) { + getDelegate().setValue(arg0, arg1); + } + + @Override + public void setValue(String arg0, String arg1) { + getDelegate().setValue(arg0, arg1); + } + + private IPreferenceStore getDelegate() { + if (!computed) { + delegate = getPreferenceStore(); + } + return delegate; + } + + private synchronized IPreferenceStore getPreferenceStore() { + if (computed) { + return delegate; + } + IPreferenceStore store = createCustomPreferenceStore(); + if (store != null) { + delegate = store; + computed = true; + } + return delegate; + } + + IPreferenceStore createCustomPreferenceStore() { + Set contentTypes = editor.getContentTypes(); + if (contentTypes == null) { + return null; + } + return createPreferenceStore(contentTypes, editor); + } +} diff --git a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/compare/CompareViewerCreator.java b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/compare/CompareViewerCreator.java index 22e9a96a5..5a488745a 100644 --- a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/compare/CompareViewerCreator.java +++ b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/compare/CompareViewerCreator.java @@ -20,7 +20,8 @@ public class CompareViewerCreator implements IViewerCreator { - @Override public Viewer createViewer(Composite parent, CompareConfiguration compareConfiguration) { + @Override + public Viewer createViewer(Composite parent, CompareConfiguration compareConfiguration) { return new GenericEditorMergeViewer(parent, compareConfiguration); } diff --git a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/compare/GenericEditorMergeViewer.java b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/compare/GenericEditorMergeViewer.java index c6a384552..7850a380f 100644 --- a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/compare/GenericEditorMergeViewer.java +++ b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/compare/GenericEditorMergeViewer.java @@ -19,17 +19,15 @@ import org.eclipse.compare.CompareConfiguration; import org.eclipse.compare.contentmergeviewer.TextMergeViewer; import org.eclipse.core.runtime.content.IContentType; -import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.ITextInputListener; import org.eclipse.jface.text.TextViewer; import org.eclipse.jface.text.source.ISourceViewer; import org.eclipse.jface.text.source.SourceViewer; import org.eclipse.swt.widgets.Composite; -import org.eclipse.ui.editors.text.EditorsUI; +import org.eclipse.ui.internal.genericeditor.ExtensionBasedTextEditor; import org.eclipse.ui.internal.genericeditor.ExtensionBasedTextViewerConfiguration; -import org.eclipse.ui.internal.genericeditor.GenericEditorPlugin; -import org.eclipse.ui.texteditor.ChainedPreferenceStore; +import org.eclipse.ui.internal.genericeditor.PreferenceStoreWrapper; public class GenericEditorMergeViewer extends TextMergeViewer { @@ -45,8 +43,7 @@ protected SourceViewer createSourceViewer(Composite parent, int textOrientation) res.addTextInputListener(new ITextInputListener() { @Override public void inputDocumentChanged(IDocument oldInput, IDocument newInput) { - fallbackContentTypes - .addAll(new ExtensionBasedTextViewerConfiguration(null, null).getContentTypes(newInput)); + fallbackContentTypes.addAll(ExtensionBasedTextEditor.collectContentTypes(newInput, null)); configureTextViewer(res); } @@ -62,8 +59,8 @@ public void inputDocumentAboutToBeChanged(IDocument oldInput, IDocument newInput protected void configureTextViewer(TextViewer textViewer) { if (textViewer.getDocument() != null && textViewer instanceof ISourceViewer) { ExtensionBasedTextViewerConfiguration configuration = new ExtensionBasedTextViewerConfiguration(null, - new ChainedPreferenceStore(new IPreferenceStore[] { EditorsUI.getPreferenceStore(), - GenericEditorPlugin.getDefault().getPreferenceStore() })); + PreferenceStoreWrapper.createPreferenceStore( + ExtensionBasedTextEditor.collectContentTypes(textViewer.getDocument(), null), null)); configuration.setFallbackContentTypes(fallbackContentTypes); ((ISourceViewer) textViewer).configure(configuration); } diff --git a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/AbstractTextEditor.java b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/AbstractTextEditor.java index 878a579a5..765375329 100644 --- a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/AbstractTextEditor.java +++ b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/AbstractTextEditor.java @@ -6175,6 +6175,10 @@ public T getAdapter(Class required) { if (ITextViewer.class.equals(required)) return (fSourceViewer == null ? null : (T) fSourceViewer); + if (IPreferenceStore.class.equals(required)) { + return (T) getPreferenceStore(); + } + return super.getAdapter(required); }