diff --git a/org.eclipse.wildwebdeveloper.tests/src/org/eclipse/wildwebdeveloper/tests/TestHTML.java b/org.eclipse.wildwebdeveloper.tests/src/org/eclipse/wildwebdeveloper/tests/TestHTML.java index 637249330d..593fc3d80f 100644 --- a/org.eclipse.wildwebdeveloper.tests/src/org/eclipse/wildwebdeveloper/tests/TestHTML.java +++ b/org.eclipse.wildwebdeveloper.tests/src/org/eclipse/wildwebdeveloper/tests/TestHTML.java @@ -22,6 +22,7 @@ import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; +import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.TextSelection; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.commands.ICommandService; @@ -71,8 +72,8 @@ public void testFormat() throws Exception { editor.setFocus(); editor.getSelectionProvider().setSelection(new TextSelection(0, 0)); IHandlerService handlerService = PlatformUI.getWorkbench().getService(IHandlerService.class); - assertTrue( - PlatformUI.getWorkbench().getService(ICommandService.class).getCommand("org.eclipse.lsp4e.format").isEnabled()); + assertTrue(PlatformUI.getWorkbench().getService(ICommandService.class).getCommand("org.eclipse.lsp4e.format") + .isEnabled()); AtomicReference ex = new AtomicReference<>(); new DisplayHelper() { @Override @@ -95,4 +96,24 @@ protected boolean condition() { } }.waitForCondition(editor.getSite().getShell().getDisplay(), 3000); } + + @Test + public void autoCloseTags() throws Exception { + final IProject project = ResourcesPlugin.getWorkspace().getRoot() + .getProject("testHTMLFile" + System.currentTimeMillis()); + project.create(null); + project.open(null); + final IFile file = project.getFile("autoCloseTags.xml"); + file.create(new ByteArrayInputStream(""); + assertTrue(new DisplayHelper() { + @Override + protected boolean condition() { + return "".equals(document.get()); + } + }.waitForCondition(PlatformUI.getWorkbench().getDisplay(), 5000), "Autoclose not done"); + } } diff --git a/org.eclipse.wildwebdeveloper.tests/src/org/eclipse/wildwebdeveloper/tests/TestXML.java b/org.eclipse.wildwebdeveloper.tests/src/org/eclipse/wildwebdeveloper/tests/TestXML.java index bb6f300cf7..10eaed521a 100644 --- a/org.eclipse.wildwebdeveloper.tests/src/org/eclipse/wildwebdeveloper/tests/TestXML.java +++ b/org.eclipse.wildwebdeveloper.tests/src/org/eclipse/wildwebdeveloper/tests/TestXML.java @@ -21,6 +21,7 @@ import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; +import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.TextSelection; import org.eclipse.jface.text.contentassist.ICompletionProposal; import org.eclipse.lsp4e.operations.completion.LSContentAssistProcessor; @@ -140,4 +141,20 @@ public void testComplexXML() throws Exception { DisplayHelper.sleep(editor.getSite().getShell().getDisplay(), 2000); assertTrue(proposals.length > 1); } + + @Test + public void autoCloseTags() throws Exception { + final IFile file = project.getFile("autoCloseTags.xml"); + file.create(new ByteArrayInputStream(""); + assertTrue(new DisplayHelper() { + @Override + protected boolean condition() { + return "".equals(document.get()); + } + }.waitForCondition(PlatformUI.getWorkbench().getDisplay(), 5000), "Autoclose not done"); + } } diff --git a/org.eclipse.wildwebdeveloper.xml/META-INF/MANIFEST.MF b/org.eclipse.wildwebdeveloper.xml/META-INF/MANIFEST.MF index c0172bae22..5f00ac03bc 100644 --- a/org.eclipse.wildwebdeveloper.xml/META-INF/MANIFEST.MF +++ b/org.eclipse.wildwebdeveloper.xml/META-INF/MANIFEST.MF @@ -20,7 +20,8 @@ Require-Bundle: org.eclipse.tm4e.registry;bundle-version="0.3.0", org.eclipse.ui.genericeditor;bundle-version="1.0.0", org.eclipse.core.net;bundle-version="1.3.0", org.eclipse.lsp4j.jsonrpc, - org.eclipse.text + org.eclipse.text, + org.eclipse.jface.text;bundle-version="3.20.100" Bundle-ActivationPolicy: lazy Bundle-Activator: org.eclipse.wildwebdeveloper.xml.internal.Activator Export-Package: org.eclipse.wildwebdeveloper.xml;x-friends:="org.eclipse.m2e.editor.lemminx" diff --git a/org.eclipse.wildwebdeveloper.xml/plugin.xml b/org.eclipse.wildwebdeveloper.xml/plugin.xml index 04a28b60b2..1228bd486e 100644 --- a/org.eclipse.wildwebdeveloper.xml/plugin.xml +++ b/org.eclipse.wildwebdeveloper.xml/plugin.xml @@ -18,6 +18,13 @@ + + + + closeTag(TextDocumentPositionParams params); + +} diff --git a/org.eclipse.wildwebdeveloper.xml/src/org/eclipse/wildwebdeveloper/xml/internal/autoclose/AutoCloseTagResponse.java b/org.eclipse.wildwebdeveloper.xml/src/org/eclipse/wildwebdeveloper/xml/internal/autoclose/AutoCloseTagResponse.java new file mode 100644 index 0000000000..15a35c8689 --- /dev/null +++ b/org.eclipse.wildwebdeveloper.xml/src/org/eclipse/wildwebdeveloper/xml/internal/autoclose/AutoCloseTagResponse.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2022 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 is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Angelo ZERR (Red Hat Inc.) - initial implementation + *******************************************************************************/ +package org.eclipse.wildwebdeveloper.xml.internal.autoclose; + +import org.eclipse.lsp4j.Range; + +/** + * Auto close tag LSP response. + * + */ +public class AutoCloseTagResponse { + + public String snippet; + public Range range; + + public AutoCloseTagResponse(String snippet, Range range) { + this.snippet = snippet; + this.range = range; + } + + public AutoCloseTagResponse(String snippet) { + this.snippet = snippet; + } +} \ No newline at end of file diff --git a/org.eclipse.wildwebdeveloper.xml/src/org/eclipse/wildwebdeveloper/xml/internal/autoclose/XMLAutoCloseTagReconciler.java b/org.eclipse.wildwebdeveloper.xml/src/org/eclipse/wildwebdeveloper/xml/internal/autoclose/XMLAutoCloseTagReconciler.java new file mode 100644 index 0000000000..a40412bb4d --- /dev/null +++ b/org.eclipse.wildwebdeveloper.xml/src/org/eclipse/wildwebdeveloper/xml/internal/autoclose/XMLAutoCloseTagReconciler.java @@ -0,0 +1,207 @@ +/******************************************************************************* + * Copyright (c) 2022 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 is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Angelo ZERR (Red Hat Inc.) - initial implementation + */ +package org.eclipse.wildwebdeveloper.xml.internal.autoclose; + +import static org.eclipse.wildwebdeveloper.xml.internal.ui.preferences.XMLPreferenceClientConstants.XML_PREFERENCES_COMPLETION_AUTO_CLOSE_TAGS; + +import java.net.URI; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.DocumentEvent; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IDocumentListener; +import org.eclipse.jface.text.ITextInputListener; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.reconciler.IReconciler; +import org.eclipse.jface.text.reconciler.IReconcilingStrategy; +import org.eclipse.lsp4e.LSPEclipseUtils; +import org.eclipse.lsp4e.LanguageServiceAccessor; +import org.eclipse.lsp4e.LanguageServiceAccessor.LSPDocumentInfo; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.Range; +import org.eclipse.lsp4j.TextDocumentIdentifier; +import org.eclipse.lsp4j.TextDocumentPositionParams; +import org.eclipse.swt.widgets.Display; +import org.eclipse.wildwebdeveloper.xml.internal.Activator; +import org.eclipse.wildwebdeveloper.xml.internal.XMLLanguageServerAPI; + +/** + * {@link IReconciler} implementation used to support auto close tags , features + * provides by the LemMinx XML language server with the custom 'xml/closeTag' + * LSP request. + * + */ +public class XMLAutoCloseTagReconciler implements IReconciler { + + private IDocument document; + + private ITextViewer viewer; + + private Listener listener; + + private void autoInsert(DocumentEvent event) { + if (!isEnabled()) { + return; + } + if (event == null || viewer == null) { + return; + } + IDocument document = event.getDocument(); + if (document == null || event == null || event.getLength() != 0 || event.getText().length() != 1) { + return; + } + + int offset = event.getOffset() + 1; + char c = event.getText().charAt(0); + if (c != '>' && c != '/') { + return; + } + URI uri = LSPEclipseUtils.toUri(document); + if (uri == null) { + return; + } + + TextDocumentIdentifier identifier = new TextDocumentIdentifier(uri.toString()); + Optional info = LanguageServiceAccessor + .getLSPDocumentInfosFor(document, (capabilities) -> true).stream() + .filter(doc -> (doc.getLanguageClient() instanceof XMLLanguageServerAPI)).findAny(); + if (!info.isEmpty()) { + // The document is bound with XML language server, consumes the xml/closeTag + final Display display = viewer.getTextWidget().getDisplay(); + CompletableFuture.supplyAsync(() -> { + try { + // Wait for textDocument/didChange + Thread.sleep(100); + } catch (InterruptedException ex) { + Thread.interrupted(); + } + try { + TextDocumentPositionParams params = LSPEclipseUtils.toTextDocumentPosistionParams(uri, offset, + document); + // consumes xml/closeTag from XML language server + ((XMLLanguageServerAPI) info.get().getLanguageClient()).closeTag(params).thenAccept(r -> { + if (r != null) { + display.asyncExec(() -> { + try { + // we receive a text like + // $0 + // $0 should be used for set the cursor. + String text = r.snippet.replace("$0", ""); + int replaceLength = getReplaceLength(r.range, document); + document.replace(offset, replaceLength, text); + } catch (BadLocationException e) { + // Do nothing + } + }); + + } + }); + } catch (BadLocationException e) { + // Do nothing + } + return null; + }); + } + } + + private boolean isEnabled() { + return Activator.getDefault().getPreferenceStore().getBoolean(XML_PREFERENCES_COMPLETION_AUTO_CLOSE_TAGS); + } + + private static int getReplaceLength(Range range, IDocument document) throws BadLocationException { + if (range == null) { + return 0; + } + Position start = range.getStart(); + Position end = range.getEnd(); + if (start.getLine() == end.getLine()) { + return end.getCharacter() - start.getCharacter(); + } + int startOffset = LSPEclipseUtils.toOffset(start, document); + int endOffset = LSPEclipseUtils.toOffset(end, document); + return endOffset - startOffset; + } + + /** + * Internal document listener and text input listener. + */ + class Listener implements IDocumentListener, ITextInputListener { + + /* + * @see IDocumentListener#documentAboutToBeChanged(DocumentEvent) + */ + @Override + public void documentAboutToBeChanged(DocumentEvent e) { + } + + /* + * @see IDocumentListener#documentChanged(DocumentEvent) + */ + @Override + public void documentChanged(DocumentEvent e) { + autoInsert(e); + } + + /* + * @see ITextInputListener#inputDocumentAboutToBeChanged(IDocument, IDocument) + */ + @Override + public void inputDocumentAboutToBeChanged(IDocument oldInput, IDocument newInput) { + if (oldInput == document) { + if (document != null) { + document.removeDocumentListener(this); + } + document = null; + } + } + + /* + * @see ITextInputListener#inputDocumentChanged(IDocument, IDocument) + */ + @Override + public void inputDocumentChanged(IDocument oldInput, IDocument newInput) { + document = newInput; + if (document == null) { + return; + } + document.addDocumentListener(this); + } + + } + + @Override + public void install(ITextViewer viewer) { + this.viewer = viewer; + listener = new Listener(); + viewer.addTextInputListener(listener); + } + + @Override + public void uninstall() { + if (listener != null) { + viewer.removeTextInputListener(listener); + if (document != null) { + document.removeDocumentListener(listener); + } + listener = null; + } + this.viewer = null; + } + + @Override + public IReconcilingStrategy getReconcilingStrategy(String contentType) { + return null; + } + +} diff --git a/org.eclipse.wildwebdeveloper.xml/src/org/eclipse/wildwebdeveloper/xml/internal/ui/Messages.java b/org.eclipse.wildwebdeveloper.xml/src/org/eclipse/wildwebdeveloper/xml/internal/ui/Messages.java index 68637f9592..617f09ab05 100644 --- a/org.eclipse.wildwebdeveloper.xml/src/org/eclipse/wildwebdeveloper/xml/internal/ui/Messages.java +++ b/org.eclipse.wildwebdeveloper.xml/src/org/eclipse/wildwebdeveloper/xml/internal/ui/Messages.java @@ -18,10 +18,9 @@ public class Messages extends NLS { // --------- XML Preference page public static String XMLPreferencePage_XMLCatalogsLink; public static String XMLPreferencePage_downloadExternalResources_enabled; + public static String XMLPreferencePage_completion_autoCloseTags; // --------- XML Catalog preference page - - public static String XMLCatalogPreferencePage_Entries; public static String XMLCatalogPreferencePage_Edit; public static String XMLCatalogPreferencePage_OpenInEditorTitle; @@ -29,11 +28,9 @@ public class Messages extends NLS { public static String XMLCatalogPreferencePage_OpenInEditorApplyAndEdit; // --------- XML CodeLens preference page - public static String XMLCodelensPreferencePage_codelens_enabled; // --------- XML Formatting preference page - public static String XMLFormattingPreferencePage_format_emptyElements; public static String XMLFormattingPreferencePage_format_emptyElements_collapse; public static String XMLFormattingPreferencePage_format_emptyElements_expand; @@ -49,7 +46,6 @@ public class Messages extends NLS { public static String XMLFormattingPreferencePage_format_joinCommentLines; // --------- XML Validation preference page - public static String XMLValidationPreferencePage_validation_enabled; public static String XMLValidationPreferencePage_validation_namespaces_enabled; public static String XMLValidationPreferencePage_validation_schema_enabled; @@ -58,7 +54,6 @@ public class Messages extends NLS { public static String XMLValidationPreferencePage_validation_noGrammar; // --------- Buttons - public static String PreferencePage_Add; public static String PreferencePage_Remove; diff --git a/org.eclipse.wildwebdeveloper.xml/src/org/eclipse/wildwebdeveloper/xml/internal/ui/messages.properties b/org.eclipse.wildwebdeveloper.xml/src/org/eclipse/wildwebdeveloper/xml/internal/ui/messages.properties index 89aeecb49c..ec2e09d562 100644 --- a/org.eclipse.wildwebdeveloper.xml/src/org/eclipse/wildwebdeveloper/xml/internal/ui/messages.properties +++ b/org.eclipse.wildwebdeveloper.xml/src/org/eclipse/wildwebdeveloper/xml/internal/ui/messages.properties @@ -12,7 +12,8 @@ # Preference page XMLPreferencePage_XMLCatalogsLink=See 'XML Catalogs' for XML catalogs preferences -XMLPreferencePage_downloadExternalResources_enabled=Download external resources like referenced DTD, XSD +XMLPreferencePage_downloadExternalResources_enabled=&Download external resources like referenced DTD, XSD +XMLPreferencePage_completion_autoCloseTags=Enable &autoclosing of XML tags # XML Catalog preference page XMLCatalogPreferencePage_Entries=Catalogs diff --git a/org.eclipse.wildwebdeveloper.xml/src/org/eclipse/wildwebdeveloper/xml/internal/ui/preferences/XMLPreferenceClientConstants.java b/org.eclipse.wildwebdeveloper.xml/src/org/eclipse/wildwebdeveloper/xml/internal/ui/preferences/XMLPreferenceClientConstants.java new file mode 100644 index 0000000000..2c72319a47 --- /dev/null +++ b/org.eclipse.wildwebdeveloper.xml/src/org/eclipse/wildwebdeveloper/xml/internal/ui/preferences/XMLPreferenceClientConstants.java @@ -0,0 +1,22 @@ +/******************************************************************************* + * Copyright (c) 2022 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 is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Angelo ZERR (Red Hat Inc.) - initial implementation + *******************************************************************************/ +package org.eclipse.wildwebdeveloper.xml.internal.ui.preferences; + +/** + * XML preference client constants. + * + */ +public class XMLPreferenceClientConstants { + + public static final String XML_PREFERENCES_COMPLETION_AUTO_CLOSE_TAGS = "completion/autoCloseTags"; +} diff --git a/org.eclipse.wildwebdeveloper.xml/src/org/eclipse/wildwebdeveloper/xml/internal/ui/preferences/XMLPreferenceConstants.java b/org.eclipse.wildwebdeveloper.xml/src/org/eclipse/wildwebdeveloper/xml/internal/ui/preferences/XMLPreferenceConstants.java index 9fd46b3480..1cd0ac96b3 100644 --- a/org.eclipse.wildwebdeveloper.xml/src/org/eclipse/wildwebdeveloper/xml/internal/ui/preferences/XMLPreferenceConstants.java +++ b/org.eclipse.wildwebdeveloper.xml/src/org/eclipse/wildwebdeveloper/xml/internal/ui/preferences/XMLPreferenceConstants.java @@ -23,6 +23,10 @@ import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.wildwebdeveloper.xml.internal.Activator; +/** + * XML preference server constants. + * + */ public class XMLPreferenceConstants { public static class LemminxPreference { diff --git a/org.eclipse.wildwebdeveloper.xml/src/org/eclipse/wildwebdeveloper/xml/internal/ui/preferences/XMLPreferenceInitializer.java b/org.eclipse.wildwebdeveloper.xml/src/org/eclipse/wildwebdeveloper/xml/internal/ui/preferences/XMLPreferenceInitializer.java index 0f5ba7addd..4247bb39cd 100644 --- a/org.eclipse.wildwebdeveloper.xml/src/org/eclipse/wildwebdeveloper/xml/internal/ui/preferences/XMLPreferenceInitializer.java +++ b/org.eclipse.wildwebdeveloper.xml/src/org/eclipse/wildwebdeveloper/xml/internal/ui/preferences/XMLPreferenceInitializer.java @@ -11,6 +11,7 @@ *******************************************************************************/ package org.eclipse.wildwebdeveloper.xml.internal.ui.preferences; +import static org.eclipse.wildwebdeveloper.xml.internal.ui.preferences.XMLPreferenceClientConstants.XML_PREFERENCES_COMPLETION_AUTO_CLOSE_TAGS; import static org.eclipse.wildwebdeveloper.xml.internal.ui.preferences.XMLPreferenceConstants.XML_PREFERENCES_CATAGLOGS; import static org.eclipse.wildwebdeveloper.xml.internal.ui.preferences.XMLPreferenceConstants.XML_PREFERENCES_CODELENS_ENABLED; import static org.eclipse.wildwebdeveloper.xml.internal.ui.preferences.XMLPreferenceConstants.XML_PREFERENCES_DOWNLOAD_EXTERNAL_RESOURCES; @@ -34,11 +35,20 @@ import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.wildwebdeveloper.xml.internal.Activator; +/** + * XML preference initializer. + * + */ public class XMLPreferenceInitializer extends AbstractPreferenceInitializer { + private static final IPreferenceStore STORE = Activator.getDefault().getPreferenceStore(); @Override public void initializeDefaultPreferences() { + // Client settings + STORE.setDefault(XML_PREFERENCES_COMPLETION_AUTO_CLOSE_TAGS, true); + + // Server settings STORE.setDefault(XML_PREFERENCES_DOWNLOAD_EXTERNAL_RESOURCES.preferenceId, true); STORE.setDefault(XML_PREFERENCES_CATAGLOGS.preferenceId, ""); STORE.setDefault(XML_PREFERENCES_CODELENS_ENABLED.preferenceId, false); diff --git a/org.eclipse.wildwebdeveloper.xml/src/org/eclipse/wildwebdeveloper/xml/internal/ui/preferences/XMLPreferencePage.java b/org.eclipse.wildwebdeveloper.xml/src/org/eclipse/wildwebdeveloper/xml/internal/ui/preferences/XMLPreferencePage.java index 2086712c45..f8b22c04f8 100644 --- a/org.eclipse.wildwebdeveloper.xml/src/org/eclipse/wildwebdeveloper/xml/internal/ui/preferences/XMLPreferencePage.java +++ b/org.eclipse.wildwebdeveloper.xml/src/org/eclipse/wildwebdeveloper/xml/internal/ui/preferences/XMLPreferencePage.java @@ -12,6 +12,7 @@ *******************************************************************************/ package org.eclipse.wildwebdeveloper.xml.internal.ui.preferences; +import static org.eclipse.wildwebdeveloper.xml.internal.ui.preferences.XMLPreferenceClientConstants.XML_PREFERENCES_COMPLETION_AUTO_CLOSE_TAGS; import static org.eclipse.wildwebdeveloper.xml.internal.ui.preferences.XMLPreferenceConstants.XML_PREFERENCES_DOWNLOAD_EXTERNAL_RESOURCES; import org.eclipse.jface.preference.BooleanFieldEditor; @@ -29,12 +30,16 @@ import org.eclipse.wildwebdeveloper.xml.internal.Activator; import org.eclipse.wildwebdeveloper.xml.internal.ui.Messages; +/** + * XML main preference page. + * + */ public class XMLPreferencePage extends FieldEditorPreferencePage implements IWorkbenchPreferencePage { public XMLPreferencePage() { super(GRID); } - + @Override protected Control createContents(Composite parent) { Composite composite = new Composite(parent, SWT.NONE); @@ -49,24 +54,26 @@ protected Control createContents(Composite parent) { @Override public void widgetSelected(SelectionEvent e) { if (getContainer() instanceof IWorkbenchPreferenceContainer container) { - container.openPage("org.eclipse.wildwebdeveloper.xml.internal.ui.preferences.XMLCatalogPreferencePage", null); + container.openPage( + "org.eclipse.wildwebdeveloper.xml.internal.ui.preferences.XMLCatalogPreferencePage", null); } } }); super.createContents(composite); return composite; } - + @Override public void init(IWorkbench workbench) { setPreferenceStore(Activator.getDefault().getPreferenceStore()); } - @Override protected void createFieldEditors() { addField(new BooleanFieldEditor(XML_PREFERENCES_DOWNLOAD_EXTERNAL_RESOURCES.preferenceId, Messages.XMLPreferencePage_downloadExternalResources_enabled, getFieldEditorParent())); - + addField(new BooleanFieldEditor(XML_PREFERENCES_COMPLETION_AUTO_CLOSE_TAGS, + Messages.XMLPreferencePage_completion_autoCloseTags, getFieldEditorParent())); + } } \ No newline at end of file diff --git a/org.eclipse.wildwebdeveloper/META-INF/MANIFEST.MF b/org.eclipse.wildwebdeveloper/META-INF/MANIFEST.MF index d5e992282e..e85bef69b3 100644 --- a/org.eclipse.wildwebdeveloper/META-INF/MANIFEST.MF +++ b/org.eclipse.wildwebdeveloper/META-INF/MANIFEST.MF @@ -1,11 +1,12 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 -Bundle-Name: Wild Web Developer: web development in Eclipse IDE +Bundle-Name: %pluginName +Bundle-Vendor: %providerName +Bundle-Localization: plugin Bundle-SymbolicName: org.eclipse.wildwebdeveloper;singleton:=true Automatic-Module-Name: org.eclipse.wildwebdeveloper Bundle-Version: 0.6.1.qualifier Bundle-Activator: org.eclipse.wildwebdeveloper.Activator -Bundle-Vendor: Eclipse Wild Web Developer project Require-Bundle: org.eclipse.ui, org.eclipse.core.runtime, org.eclipse.lsp4e;bundle-version="0.13.6", diff --git a/org.eclipse.wildwebdeveloper/build.properties b/org.eclipse.wildwebdeveloper/build.properties index fb5e518c9e..4746789f00 100644 --- a/org.eclipse.wildwebdeveloper/build.properties +++ b/org.eclipse.wildwebdeveloper/build.properties @@ -9,6 +9,7 @@ bin.includes = META-INF/,\ grammars/,\ snippets/,\ icons/,\ - schema/ + schema/,\ + plugin.properties # See https://github.com/redhat-developer/yaml-language-server/issues/253 bin.excludes = node_modules/yaml-language-server/out/server/node_modules/ diff --git a/org.eclipse.wildwebdeveloper/plugin.properties b/org.eclipse.wildwebdeveloper/plugin.properties new file mode 100644 index 0000000000..1ac82334d7 --- /dev/null +++ b/org.eclipse.wildwebdeveloper/plugin.properties @@ -0,0 +1,15 @@ +#/******************************************************************************* +# * Copyright (c) 2022 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 is available at https://www.eclipse.org/legal/epl-2.0/ +# * +# * SPDX-License-Identifier: EPL-2.0 +# * +# * Contributors: +# * Angelo ZERR (Red Hat Inc.) - initial implementation +# *******************************************************************************/ +pluginName=Wild Web Developer: web development in Eclipse IDE +providerName=Eclipse Wild Web Developer project + +HTMLPreferencePage.name=HTML (Wild Web Developer) \ No newline at end of file diff --git a/org.eclipse.wildwebdeveloper/plugin.xml b/org.eclipse.wildwebdeveloper/plugin.xml index e2ab1736f8..d5c10fe19b 100644 --- a/org.eclipse.wildwebdeveloper/plugin.xml +++ b/org.eclipse.wildwebdeveloper/plugin.xml @@ -282,6 +282,7 @@ point="org.eclipse.lsp4e.languageServer"> @@ -325,6 +326,32 @@ + + + + + + + + + + + + + + + diff --git a/org.eclipse.wildwebdeveloper/src/org/eclipse/wildwebdeveloper/html/HTMLLanguageServerAPI.java b/org.eclipse.wildwebdeveloper/src/org/eclipse/wildwebdeveloper/html/HTMLLanguageServerAPI.java new file mode 100644 index 0000000000..8ffcd8ea12 --- /dev/null +++ b/org.eclipse.wildwebdeveloper/src/org/eclipse/wildwebdeveloper/html/HTMLLanguageServerAPI.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * Copyright (c) 2022 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 is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Angelo ZERR (Red Hat Inc.) - initial implementation + *******************************************************************************/ +package org.eclipse.wildwebdeveloper.html; + +import java.util.concurrent.CompletableFuture; + +import org.eclipse.lsp4j.jsonrpc.services.JsonRequest; +import org.eclipse.lsp4j.services.LanguageServer; +import org.eclipse.wildwebdeveloper.html.autoinsert.AutoInsertParams; + +/** + * HTML language server API which defines custom LSP commands. + * + */ +public interface HTMLLanguageServerAPI extends LanguageServer { + + /** + * Auto insert custom LSP command provided by the HTML language server to + * support auto close tag and auto insert of quote for attribute. + * + * @param params auto insert parameters. + * @return the content with the auto close tag / auto insert of quote and null + * otherwise. + * + * @see https://github.com/microsoft/vscode/blob/9b80ed652434a6e82b3ac28c1a9a01132c8faea3/extensions/html-language-features/client/src/htmlClient.ts#L46 + */ + @JsonRequest("html/autoInsert") + CompletableFuture autoInsert(AutoInsertParams params); + +} diff --git a/org.eclipse.wildwebdeveloper/src/org/eclipse/wildwebdeveloper/html/autoinsert/AutoInsertParams.java b/org.eclipse.wildwebdeveloper/src/org/eclipse/wildwebdeveloper/html/autoinsert/AutoInsertParams.java new file mode 100644 index 0000000000..265bca7ff5 --- /dev/null +++ b/org.eclipse.wildwebdeveloper/src/org/eclipse/wildwebdeveloper/html/autoinsert/AutoInsertParams.java @@ -0,0 +1,67 @@ +/******************************************************************************* + * Copyright (c) 2022 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 is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Angelo ZERR (Red Hat Inc.) - initial implementation + *******************************************************************************/ +package org.eclipse.wildwebdeveloper.html.autoinsert; + +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.TextDocumentIdentifier; + +/** + * HTML Auto Insert parameters. + * + * @see https://github.com/microsoft/vscode/blob/9b80ed652434a6e82b3ac28c1a9a01132c8faea3/extensions/html-language-features/client/src/htmlClient.ts#L31 + */ +public class AutoInsertParams { + + public enum AutoInsertKind { + + autoQuote, autoClose; + } + + /** + * The auto insert kind + */ + private String kind; // 'autoQuote' | 'autoClose'; + /** + * The text document. + */ + private TextDocumentIdentifier textDocument; + /** + * The position inside the text document. + */ + private Position position; + + public String getKind() { + return kind; + } + + public void setKind(String kind) { + this.kind = kind; + } + + public TextDocumentIdentifier getTextDocument() { + return textDocument; + } + + public void setTextDocument(TextDocumentIdentifier textDocument) { + this.textDocument = textDocument; + } + + public Position getPosition() { + return position; + } + + public void setPosition(Position position) { + this.position = position; + } + +} diff --git a/org.eclipse.wildwebdeveloper/src/org/eclipse/wildwebdeveloper/html/autoinsert/HTMLAutoInsertReconciler.java b/org.eclipse.wildwebdeveloper/src/org/eclipse/wildwebdeveloper/html/autoinsert/HTMLAutoInsertReconciler.java new file mode 100644 index 0000000000..742ede595c --- /dev/null +++ b/org.eclipse.wildwebdeveloper/src/org/eclipse/wildwebdeveloper/html/autoinsert/HTMLAutoInsertReconciler.java @@ -0,0 +1,200 @@ +/******************************************************************************* + * Copyright (c) 2022 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 is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Angelo ZERR (Red Hat Inc.) - initial implementation + *******************************************************************************/ +package org.eclipse.wildwebdeveloper.html.autoinsert; + +import static org.eclipse.wildwebdeveloper.html.ui.preferences.HTMLPreferenceClientConstants.HTML_PREFERENCES_AUTO_CLOSING_TAGS; + +import java.net.URI; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.DocumentEvent; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IDocumentListener; +import org.eclipse.jface.text.ITextInputListener; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.reconciler.IReconciler; +import org.eclipse.jface.text.reconciler.IReconcilingStrategy; +import org.eclipse.lsp4e.LSPEclipseUtils; +import org.eclipse.lsp4e.LanguageServiceAccessor; +import org.eclipse.lsp4e.LanguageServiceAccessor.LSPDocumentInfo; +import org.eclipse.lsp4j.TextDocumentIdentifier; +import org.eclipse.swt.widgets.Display; +import org.eclipse.wildwebdeveloper.Activator; +import org.eclipse.wildwebdeveloper.html.HTMLLanguageServerAPI; +import org.eclipse.wildwebdeveloper.html.autoinsert.AutoInsertParams.AutoInsertKind; + +/** + * {@link IReconciler} implementation used to support auto close tags / auto + * insert quote, features provides by the vscode HTML language server with the + * custom 'html/autoInsert' LSP request. + * + */ +public class HTMLAutoInsertReconciler implements IReconciler { + + private IDocument document; + + private ITextViewer viewer; + + private Listener listener; + + private void autoInsert(DocumentEvent event) { + if (!isEnabled()) { + return; + } + if (event == null || viewer == null) { + return; + } + IDocument document = event.getDocument(); + if (document == null || event == null || event.getLength() != 0 || event.getText().length() != 1) { + return; + } + + int offset = event.getOffset() + 1; + char c = event.getText().charAt(0); + if (c != '>' && c != '/' && c != '=') { + return; + } + URI uri = LSPEclipseUtils.toUri(document); + if (uri == null) { + return; + } + + TextDocumentIdentifier identifier = new TextDocumentIdentifier(uri.toString()); + Optional info = LanguageServiceAccessor + .getLSPDocumentInfosFor(document, (capabilities) -> true).stream() + .filter(doc -> (doc.getLanguageClient() instanceof HTMLLanguageServerAPI)).findAny(); + if (!info.isEmpty()) { + // The document is bound with HTML language server, consumes the html/autoInsert + final Display display = viewer.getTextWidget().getDisplay(); + CompletableFuture.supplyAsync(() -> { + try { + // Wait for textDocument/didChange + Thread.sleep(100); + } catch (InterruptedException ex) { + Thread.interrupted(); + } + try { + AutoInsertParams params = new AutoInsertParams(); + params.setTextDocument(identifier); + params.setKind(c == '=' ? AutoInsertKind.autoQuote.name() : AutoInsertKind.autoClose.name()); + params.setPosition(LSPEclipseUtils.toPosition(offset, document)); + + // consumes html/autoInsert from HTML language server + ((HTMLLanguageServerAPI) info.get().getLanguageClient()).autoInsert(params).thenAccept(r -> { + if (r != null) { + display.asyncExec(() -> { + try { + // we receive a text like + // $0 + // $0 should be used for set the cursor. + String text = r.replace("$0", "").replace("$1", ""); + int index = r.indexOf("$1"); + + int replaceLength = 0; + document.replace(offset, replaceLength, text); + if (index != -1) { + viewer.setSelectedRange(offset + index, 0); + } + // viewer.setSelectedRange(offset, c) + } catch (BadLocationException e) { + // Do nothing + } + }); + + } + }); + } catch (BadLocationException e) { + // Do nothing + } + return null; + }); + } + } + + private boolean isEnabled() { + return Activator.getDefault().getPreferenceStore().getBoolean(HTML_PREFERENCES_AUTO_CLOSING_TAGS); + } + + /** + * Internal document listener and text input listener. + */ + class Listener implements IDocumentListener, ITextInputListener { + + /* + * @see IDocumentListener#documentAboutToBeChanged(DocumentEvent) + */ + @Override + public void documentAboutToBeChanged(DocumentEvent e) { + } + + /* + * @see IDocumentListener#documentChanged(DocumentEvent) + */ + @Override + public void documentChanged(DocumentEvent e) { + autoInsert(e); + } + + /* + * @see ITextInputListener#inputDocumentAboutToBeChanged(IDocument, IDocument) + */ + @Override + public void inputDocumentAboutToBeChanged(IDocument oldInput, IDocument newInput) { + if (oldInput == document) { + if (document != null) { + document.removeDocumentListener(this); + } + document = null; + } + } + + /* + * @see ITextInputListener#inputDocumentChanged(IDocument, IDocument) + */ + @Override + public void inputDocumentChanged(IDocument oldInput, IDocument newInput) { + document = newInput; + if (document == null) { + return; + } + document.addDocumentListener(this); + } + + } + + @Override + public void install(ITextViewer viewer) { + this.viewer = viewer; + listener = new Listener(); + viewer.addTextInputListener(listener); + } + + @Override + public void uninstall() { + if (listener != null) { + viewer.removeTextInputListener(listener); + if (document != null) { + document.removeDocumentListener(listener); + } + listener = null; + } + this.viewer = null; + } + + @Override + public IReconcilingStrategy getReconcilingStrategy(String contentType) { + return null; + } + +} diff --git a/org.eclipse.wildwebdeveloper/src/org/eclipse/wildwebdeveloper/html/ui/Messages.java b/org.eclipse.wildwebdeveloper/src/org/eclipse/wildwebdeveloper/html/ui/Messages.java new file mode 100644 index 0000000000..94ebd2f5da --- /dev/null +++ b/org.eclipse.wildwebdeveloper/src/org/eclipse/wildwebdeveloper/html/ui/Messages.java @@ -0,0 +1,24 @@ +/******************************************************************************* + * Copyright (c) 2022 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 is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Angelo ZERR (Red Hat Inc.) - initial implementation + *******************************************************************************/ +package org.eclipse.wildwebdeveloper.html.ui; + +import org.eclipse.osgi.util.NLS; + +public class Messages extends NLS { + + // --------- HTML Preference page + public static String HTMLPreferencePage_autoClosingTags; + + static { + NLS.initializeMessages("org.eclipse.wildwebdeveloper.html.ui.messages", Messages.class); //$NON-NLS-1$ + } +} diff --git a/org.eclipse.wildwebdeveloper/src/org/eclipse/wildwebdeveloper/html/ui/messages.properties b/org.eclipse.wildwebdeveloper/src/org/eclipse/wildwebdeveloper/html/ui/messages.properties new file mode 100644 index 0000000000..f293cef9ce --- /dev/null +++ b/org.eclipse.wildwebdeveloper/src/org/eclipse/wildwebdeveloper/html/ui/messages.properties @@ -0,0 +1,14 @@ +#/******************************************************************************* +# * Copyright (c) 2022 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 is available at https://www.eclipse.org/legal/epl-2.0/ +# * +# * SPDX-License-Identifier: EPL-2.0 +# * +# * Contributors: +# * Angelo ZERR (Red Hat Inc.) - initial implementation +# *******************************************************************************/ + +# Preference page +HTMLPreferencePage_autoClosingTags=Enable autoclosing of HTML tags \ No newline at end of file diff --git a/org.eclipse.wildwebdeveloper/src/org/eclipse/wildwebdeveloper/html/ui/preferences/HTMLPreferenceClientConstants.java b/org.eclipse.wildwebdeveloper/src/org/eclipse/wildwebdeveloper/html/ui/preferences/HTMLPreferenceClientConstants.java new file mode 100644 index 0000000000..3356b71a72 --- /dev/null +++ b/org.eclipse.wildwebdeveloper/src/org/eclipse/wildwebdeveloper/html/ui/preferences/HTMLPreferenceClientConstants.java @@ -0,0 +1,23 @@ +/******************************************************************************* + * Copyright (c) 2022 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 is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Angelo ZERR (Red Hat Inc.) - initial implementation + *******************************************************************************/ +package org.eclipse.wildwebdeveloper.html.ui.preferences; + +/** + * HTML preference client constants. + * + */ +public class HTMLPreferenceClientConstants { + + public static final String HTML_PREFERENCES_AUTO_CLOSING_TAGS = "wildwebdeveloper.html.autoClosingTags"; + +} diff --git a/org.eclipse.wildwebdeveloper/src/org/eclipse/wildwebdeveloper/html/ui/preferences/HTMLPreferenceInitializer.java b/org.eclipse.wildwebdeveloper/src/org/eclipse/wildwebdeveloper/html/ui/preferences/HTMLPreferenceInitializer.java new file mode 100644 index 0000000000..01c3290007 --- /dev/null +++ b/org.eclipse.wildwebdeveloper/src/org/eclipse/wildwebdeveloper/html/ui/preferences/HTMLPreferenceInitializer.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * Copyright (c) 2022 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 is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Angelo ZERR (Red Hat Inc.) - initial implementation + *******************************************************************************/ +package org.eclipse.wildwebdeveloper.html.ui.preferences; + +import static org.eclipse.wildwebdeveloper.html.ui.preferences.HTMLPreferenceClientConstants.HTML_PREFERENCES_AUTO_CLOSING_TAGS; + +import org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.wildwebdeveloper.Activator; + +/** + * HTML preference initializer. + * + */ +public class HTMLPreferenceInitializer extends AbstractPreferenceInitializer { + + private static final IPreferenceStore STORE = Activator.getDefault().getPreferenceStore(); + + @Override + public void initializeDefaultPreferences() { + // Client settings + STORE.setDefault(HTML_PREFERENCES_AUTO_CLOSING_TAGS, true); + } + +} \ No newline at end of file diff --git a/org.eclipse.wildwebdeveloper/src/org/eclipse/wildwebdeveloper/html/ui/preferences/HTMLPreferencePage.java b/org.eclipse.wildwebdeveloper/src/org/eclipse/wildwebdeveloper/html/ui/preferences/HTMLPreferencePage.java new file mode 100644 index 0000000000..d871d3192c --- /dev/null +++ b/org.eclipse.wildwebdeveloper/src/org/eclipse/wildwebdeveloper/html/ui/preferences/HTMLPreferencePage.java @@ -0,0 +1,45 @@ +/******************************************************************************* + * Copyright (c) 2022 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 is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Angelo ZERR (Red Hat Inc.) - initial implementation + *******************************************************************************/ +package org.eclipse.wildwebdeveloper.html.ui.preferences; + +import static org.eclipse.wildwebdeveloper.html.ui.preferences.HTMLPreferenceClientConstants.HTML_PREFERENCES_AUTO_CLOSING_TAGS; + +import org.eclipse.jface.preference.BooleanFieldEditor; +import org.eclipse.jface.preference.FieldEditorPreferencePage; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.IWorkbenchPreferencePage; +import org.eclipse.wildwebdeveloper.Activator; +import org.eclipse.wildwebdeveloper.html.ui.Messages; + +/** + * HTML main preference page. + * + */ +public class HTMLPreferencePage extends FieldEditorPreferencePage implements IWorkbenchPreferencePage { + + public HTMLPreferencePage() { + super(GRID); + } + + @Override + public void init(IWorkbench workbench) { + setPreferenceStore(Activator.getDefault().getPreferenceStore()); + } + + @Override + protected void createFieldEditors() { + addField(new BooleanFieldEditor(HTML_PREFERENCES_AUTO_CLOSING_TAGS, + Messages.HTMLPreferencePage_autoClosingTags, getFieldEditorParent())); + + } +} \ No newline at end of file