diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLTextDocumentService.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLTextDocumentService.java
index 9f2ecf911..8827a1bf9 100644
--- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLTextDocumentService.java
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLTextDocumentService.java
@@ -1,5 +1,5 @@
/**
- * Copyright (c) 2018 Angelo ZERR.
+ * Copyright (c) 2018, 2026 Angelo ZERR.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
@@ -85,6 +85,10 @@
import org.eclipse.lsp4j.FoldingRangeRequestParams;
import org.eclipse.lsp4j.Hover;
import org.eclipse.lsp4j.HoverParams;
+import org.eclipse.lsp4j.InlineCompletionContext;
+import org.eclipse.lsp4j.InlineCompletionItem;
+import org.eclipse.lsp4j.InlineCompletionList;
+import org.eclipse.lsp4j.InlineCompletionParams;
import org.eclipse.lsp4j.LinkedEditingRangeParams;
import org.eclipse.lsp4j.LinkedEditingRanges;
import org.eclipse.lsp4j.Location;
@@ -275,6 +279,16 @@ public CompletableFuture resolveCompletionItem(CompletionItem un
});
}
+ @Override
+ public CompletableFuture, InlineCompletionList>> inlineCompletion(InlineCompletionParams params) {
+ return computeDOMAsync(params.getTextDocument(), (xmlDocument, cancelChecker) -> {
+ InlineCompletionContext context = params.getContext();
+ InlineCompletionList list = getXMLLanguageService().doInlineCompletion(xmlDocument, params.getPosition(), context,
+ sharedSettings, cancelChecker);
+ return Either.forRight(list);
+ });
+ }
+
@Override
public CompletableFuture hover(HoverParams params) {
return computeDOMAsync(params.getTextDocument(), (xmlDocument, cancelChecker) -> {
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/core/XMLCorePlugin.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/core/XMLCorePlugin.java
new file mode 100644
index 000000000..d13996e1a
--- /dev/null
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/core/XMLCorePlugin.java
@@ -0,0 +1,52 @@
+/**
+ * Copyright (c) 2026 Red Hat Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Red Hat Inc. - initial API and implementation
+ */
+package org.eclipse.lemminx.extensions.core;
+
+import org.eclipse.lemminx.extensions.core.participants.inlinecompletion.XMLCloseTagInlineCompletionParticipant;
+import org.eclipse.lemminx.services.extensions.IXMLExtension;
+import org.eclipse.lemminx.services.extensions.XMLExtensionsRegistry;
+import org.eclipse.lemminx.services.extensions.inlinecompletion.IInlineCompletionParticipant;
+import org.eclipse.lemminx.services.extensions.save.ISaveContext;
+import org.eclipse.lsp4j.InitializeParams;
+
+/**
+ * XML Core plugin extension to provide core XML editing features that are
+ * not specific to any grammar type (XSD, DTD, etc.).
+ *
+ *
+ * This plugin provides:
+ *
+ * - Inline completion for closing tags
+ *
+ *
+ */
+public class XMLCorePlugin implements IXMLExtension {
+
+ private IInlineCompletionParticipant inlineCompletionParticipant;
+
+ @Override
+ public void start(InitializeParams params, XMLExtensionsRegistry registry) {
+ inlineCompletionParticipant = new XMLCloseTagInlineCompletionParticipant();
+ registry.registerInlineCompletionParticipant(inlineCompletionParticipant);
+ }
+
+ @Override
+ public void stop(XMLExtensionsRegistry registry) {
+ registry.unregisterInlineCompletionParticipant(inlineCompletionParticipant);
+ }
+
+ @Override
+ public void doSave(ISaveContext context) {
+ // No settings to save
+ }
+}
\ No newline at end of file
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/core/participants/inlinecompletion/XMLCloseTagInlineCompletionParticipant.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/core/participants/inlinecompletion/XMLCloseTagInlineCompletionParticipant.java
new file mode 100644
index 000000000..9bbc68bff
--- /dev/null
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/core/participants/inlinecompletion/XMLCloseTagInlineCompletionParticipant.java
@@ -0,0 +1,74 @@
+/**
+ * Copyright (c) 2026 Red Hat Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Red Hat Inc. - initial API and implementation
+ */
+package org.eclipse.lemminx.extensions.core.participants.inlinecompletion;
+
+import static org.eclipse.lemminx.dom.parser.Constants._FSL;
+import static org.eclipse.lemminx.dom.parser.Constants._LAN;
+import static org.eclipse.lemminx.dom.parser.Constants._RAN;
+
+import org.eclipse.lemminx.dom.DOMDocument;
+import org.eclipse.lemminx.dom.DOMElement;
+import org.eclipse.lemminx.dom.DOMNode;
+import org.eclipse.lemminx.services.extensions.inlinecompletion.IInlineCompletionParticipant;
+import org.eclipse.lemminx.services.extensions.inlinecompletion.IInlineCompletionRequest;
+import org.eclipse.lemminx.services.extensions.inlinecompletion.IInlineCompletionResponse;
+import org.eclipse.lemminx.utils.XMLPositionUtility;
+import org.eclipse.lsp4j.InlineCompletionItem;
+import org.eclipse.lsp4j.jsonrpc.CancelChecker;
+
+/**
+ * Inline completion participant that suggests closing tags for open XML
+ * elements.
+ *
+ * This participant provides inline completion suggestions when the user is
+ * typing inside an XML element that needs to be closed.
+ */
+public class XMLCloseTagInlineCompletionParticipant implements IInlineCompletionParticipant {
+
+ @Override
+ public void onInlineCompletion(IInlineCompletionRequest request, IInlineCompletionResponse response,
+ CancelChecker cancelChecker) {
+
+ DOMDocument document = request.getXMLDocument();
+ int offset = request.getOffset();
+
+ // Find the node at the current position
+ DOMNode node = document.findNodeAt(offset);
+ if (node == null) {
+ return;
+ }
+
+ // Check if the cursor is at a position where we should suggest a closing tag
+ String text = document.getText();
+ if (offset > 0 && offset <= text.length()) {
+ int charBefore = text.codePointAt(offset - 1);
+
+ // Suggest closing tag after '>' or after content
+ if (charBefore == _RAN || Character.isLetterOrDigit(charBefore) || Character.isWhitespace(charBefore)) {
+ // Check if we're inside an element that needs closing
+ DOMElement parentElement = XMLPositionUtility.findUnclosedParentElement(node, offset);
+ if (parentElement != null) {
+ String tagName = parentElement.getTagName();
+ if (tagName != null && !tagName.isEmpty()) {
+ String closingTag = Character.toString(_LAN) + Character.toString(_FSL) + tagName
+ + Character.toString(_RAN);
+
+ InlineCompletionItem item = new InlineCompletionItem();
+ item.setInsertText(closingTag);
+ response.addInlineCompletionItem(item);
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/InlineCompletionRequest.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/InlineCompletionRequest.java
new file mode 100644
index 000000000..8586a25d5
--- /dev/null
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/InlineCompletionRequest.java
@@ -0,0 +1,55 @@
+/**
+ * Copyright (c) 2026 Red Hat Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Red Hat Inc. - initial API and implementation
+ */
+package org.eclipse.lemminx.services;
+
+import org.eclipse.lemminx.commons.BadLocationException;
+import org.eclipse.lemminx.dom.DOMDocument;
+import org.eclipse.lemminx.services.extensions.XMLExtensionsRegistry;
+import org.eclipse.lemminx.services.extensions.inlinecompletion.IInlineCompletionRequest;
+import org.eclipse.lemminx.settings.SharedSettings;
+import org.eclipse.lsp4j.InlineCompletionContext;
+import org.eclipse.lsp4j.Position;
+
+/**
+ * Inline completion request implementation.
+ */
+class InlineCompletionRequest extends AbstractPositionRequest implements IInlineCompletionRequest {
+
+ private final InlineCompletionContext context;
+ private final SharedSettings sharedSettings;
+
+ public InlineCompletionRequest(DOMDocument xmlDocument, Position position,
+ InlineCompletionContext context,
+ SharedSettings settings,
+ XMLExtensionsRegistry extensionsRegistry) throws BadLocationException {
+ super(xmlDocument, position, extensionsRegistry);
+ this.context = context;
+ this.sharedSettings = settings;
+ }
+
+ @Override
+ public InlineCompletionContext getContext() {
+ return context;
+ }
+
+ @Override
+ public SharedSettings getSharedSettings() {
+ return sharedSettings;
+ }
+
+ @Override
+ public boolean canSupportMarkupKind(String kind) {
+ // Inline completion typically doesn't use markup documentation
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/InlineCompletionResponse.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/InlineCompletionResponse.java
new file mode 100644
index 000000000..0e9e25354
--- /dev/null
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/InlineCompletionResponse.java
@@ -0,0 +1,45 @@
+/**
+ * Copyright (c) 2026 Red Hat Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Red Hat Inc. - initial API and implementation
+ */
+package org.eclipse.lemminx.services;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.lemminx.services.extensions.inlinecompletion.IInlineCompletionResponse;
+import org.eclipse.lsp4j.InlineCompletionItem;
+
+/**
+ * Inline completion response implementation.
+ */
+class InlineCompletionResponse implements IInlineCompletionResponse {
+
+ private final List items;
+
+ public InlineCompletionResponse() {
+ this.items = new ArrayList<>();
+ }
+
+ @Override
+ public void addInlineCompletionItem(InlineCompletionItem item) {
+ items.add(item);
+ }
+
+ /**
+ * Returns the list of inline completion items.
+ *
+ * @return the list of inline completion items
+ */
+ public List getItems() {
+ return items;
+ }
+}
\ No newline at end of file
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLInlineCompletion.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLInlineCompletion.java
new file mode 100644
index 000000000..3f9956a14
--- /dev/null
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLInlineCompletion.java
@@ -0,0 +1,86 @@
+/**
+ * Copyright (c) 2026 Red Hat Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Red Hat Inc. - initial API and implementation
+ */
+package org.eclipse.lemminx.services;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CancellationException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.eclipse.lemminx.commons.BadLocationException;
+import org.eclipse.lemminx.dom.DOMDocument;
+import org.eclipse.lemminx.services.extensions.XMLExtensionsRegistry;
+import org.eclipse.lemminx.services.extensions.inlinecompletion.IInlineCompletionParticipant;
+import org.eclipse.lemminx.settings.SharedSettings;
+import org.eclipse.lsp4j.InlineCompletionContext;
+import org.eclipse.lsp4j.InlineCompletionItem;
+import org.eclipse.lsp4j.InlineCompletionList;
+import org.eclipse.lsp4j.Position;
+import org.eclipse.lsp4j.jsonrpc.CancelChecker;
+
+/**
+ * XML inline completion service.
+ */
+public class XMLInlineCompletion {
+
+ private static final Logger LOGGER = Logger.getLogger(XMLInlineCompletion.class.getName());
+
+ private final XMLExtensionsRegistry extensionsRegistry;
+
+ public XMLInlineCompletion(XMLExtensionsRegistry extensionsRegistry) {
+ this.extensionsRegistry = extensionsRegistry;
+ }
+
+ /**
+ * Returns inline completion items for the given document and position.
+ *
+ * @param document the DOM document
+ * @param position the position where inline completion is requested
+ * @param context the inline completion context
+ * @param settings the shared settings
+ * @param cancelChecker the cancel checker
+ * @return the inline completion list
+ */
+ public InlineCompletionList doInlineCompletion(DOMDocument document, Position position,
+ InlineCompletionContext context,
+ SharedSettings settings,
+ CancelChecker cancelChecker) {
+ InlineCompletionResponse response = new InlineCompletionResponse();
+
+ try {
+ InlineCompletionRequest request = new InlineCompletionRequest(document, position, context, settings,
+ extensionsRegistry);
+
+ // Call all registered inline completion participants
+ for (IInlineCompletionParticipant participant : extensionsRegistry.getInlineCompletionParticipants()) {
+ try {
+ cancelChecker.checkCanceled();
+ participant.onInlineCompletion(request, response, cancelChecker);
+ } catch (CancellationException e) {
+ throw e;
+ } catch (Exception e) {
+ LOGGER.log(Level.SEVERE,
+ "Error while processing inline completion participant " + participant.getClass().getName(),
+ e);
+ }
+ }
+ } catch (BadLocationException e) {
+ LOGGER.log(Level.SEVERE, "Error while computing inline completion", e);
+ }
+
+ InlineCompletionList result = new InlineCompletionList();
+ result.setItems(response.getItems());
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLLanguageService.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLLanguageService.java
index 2ca98f708..ea71c25f7 100644
--- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLLanguageService.java
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLLanguageService.java
@@ -1,5 +1,5 @@
/**
- * Copyright (c) 2018, 2023 Angelo ZERR
+ * Copyright (c) 2018, 2026 Angelo ZERR
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
@@ -47,6 +47,8 @@
import org.eclipse.lsp4j.DocumentSymbol;
import org.eclipse.lsp4j.FoldingRange;
import org.eclipse.lsp4j.Hover;
+import org.eclipse.lsp4j.InlineCompletionContext;
+import org.eclipse.lsp4j.InlineCompletionList;
import org.eclipse.lsp4j.LinkedEditingRanges;
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.LocationLink;
@@ -95,6 +97,7 @@ public void checkCanceled() {
private final XMLSelectionRanges selectionRanges;
private final XMLLinkedEditing linkedEditing;
private final XMLDocumentColor documentColor;
+ private final XMLInlineCompletion inlineCompletion;
public XMLLanguageService() {
this.formatter = new XMLFormatter(this);
@@ -116,6 +119,7 @@ public XMLLanguageService() {
this.rename = new XMLRename(this);
this.selectionRanges = new XMLSelectionRanges();
this.linkedEditing = new XMLLinkedEditing(this);
+ this.inlineCompletion = new XMLInlineCompletion(this);
}
@Override
@@ -194,6 +198,16 @@ public CompletionItem resolveCompletionItem(CompletionItem unresolved, DOMDocume
SharedSettings sharedSettings, CancelChecker cancelChecker) {
return completions.resolveCompletionItem(unresolved, xmlDocument, sharedSettings, cancelChecker);
}
+ public InlineCompletionList doInlineCompletion(DOMDocument xmlDocument, Position position,
+ InlineCompletionContext context, SharedSettings settings) {
+ return doInlineCompletion(xmlDocument, position, context, settings, NULL_CHECKER);
+ }
+
+ public InlineCompletionList doInlineCompletion(DOMDocument xmlDocument, Position position,
+ InlineCompletionContext context, SharedSettings settings, CancelChecker cancelChecker) {
+ return inlineCompletion.doInlineCompletion(xmlDocument, position, context, settings, cancelChecker);
+ }
+
public Hover doHover(DOMDocument xmlDocument, Position position, SharedSettings sharedSettings) {
return doHover(xmlDocument, position, sharedSettings, NULL_CHECKER);
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/XMLExtensionsRegistry.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/XMLExtensionsRegistry.java
index 7cb1a458a..6bc8efead 100644
--- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/XMLExtensionsRegistry.java
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/XMLExtensionsRegistry.java
@@ -1,5 +1,5 @@
/**
- * Copyright (c) 2018, 2023 Angelo ZERR
+ * Copyright (c) 2018, 2026 Angelo ZERR
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
@@ -34,6 +34,7 @@
import org.eclipse.lemminx.services.extensions.diagnostics.IDiagnosticsParticipant;
import org.eclipse.lemminx.services.extensions.format.IFormatterParticipant;
import org.eclipse.lemminx.services.extensions.hover.IHoverParticipant;
+import org.eclipse.lemminx.services.extensions.inlinecompletion.IInlineCompletionParticipant;
import org.eclipse.lemminx.services.extensions.rename.IRenameParticipant;
import org.eclipse.lemminx.services.extensions.save.ISaveContext;
import org.eclipse.lemminx.services.extensions.save.ISaveContext.SaveContextType;
@@ -68,6 +69,7 @@ public class XMLExtensionsRegistry implements IComponentProvider {
private final List symbolsProviderParticipants;
private final List workspaceServiceParticipants;
private final List documentLifecycleParticipants;
+ private final List inlineCompletionParticipants;
private IXMLDocumentProvider documentProvider;
private IXMLValidationService validationService;
private IXMLCommandService commandService;
@@ -104,6 +106,7 @@ public XMLExtensionsRegistry() {
symbolsProviderParticipants = new ArrayList<>();
workspaceServiceParticipants = new ArrayList<>();
documentLifecycleParticipants = new ArrayList<>();
+ inlineCompletionParticipants = new ArrayList<>();
resolverExtensionManager = new URIResolverExtensionManager();
components = new HashMap<>();
telemetryManager = new TelemetryManager(null);
@@ -248,6 +251,16 @@ public List getDocumentLifecycleParticipants() {
return documentLifecycleParticipants;
}
+ /**
+ * Return the registered inline completion participants.
+ *
+ * @return the registered inline completion participants.
+ */
+ public List getInlineCompletionParticipants() {
+ initializeIfNeeded();
+ return inlineCompletionParticipants;
+ }
+
public void initializeIfNeeded() {
if (initialized) {
return;
@@ -470,6 +483,24 @@ public void registerDocumentLifecycleParticipant(IDocumentLifecycleParticipant d
public void unregisterDocumentLifecycleParticipant(IDocumentLifecycleParticipant documentLifecycleParticipant) {
documentLifecycleParticipants.remove(documentLifecycleParticipant);
}
+ /**
+ * Register a new inline completion participant
+ *
+ * @param inlineCompletionParticipant the participant to register
+ */
+ public void registerInlineCompletionParticipant(IInlineCompletionParticipant inlineCompletionParticipant) {
+ inlineCompletionParticipants.add(inlineCompletionParticipant);
+ }
+
+ /**
+ * Unregister an inline completion participant.
+ *
+ * @param inlineCompletionParticipant the participant to unregister
+ */
+ public void unregisterInlineCompletionParticipant(IInlineCompletionParticipant inlineCompletionParticipant) {
+ inlineCompletionParticipants.remove(inlineCompletionParticipant);
+ }
+
/**
* Returns the XML Document provider and null otherwise.
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/inlinecompletion/IInlineCompletionParticipant.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/inlinecompletion/IInlineCompletionParticipant.java
new file mode 100644
index 000000000..53c8f5d54
--- /dev/null
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/inlinecompletion/IInlineCompletionParticipant.java
@@ -0,0 +1,37 @@
+/**
+ * Copyright (c) 2026 Red Hat Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Red Hat Inc. - initial API and implementation
+ */
+package org.eclipse.lemminx.services.extensions.inlinecompletion;
+
+import org.eclipse.lsp4j.jsonrpc.CancelChecker;
+
+/**
+ * Inline completion participant API.
+ *
+ *
+ * This participant is called when inline completion is requested to provide
+ * context-aware suggestions as users type.
+ *
+ */
+public interface IInlineCompletionParticipant {
+
+ /**
+ * Called when inline completion is requested.
+ *
+ * @param request the inline completion request
+ * @param response the response to add inline completion items to
+ * @param cancelChecker the cancel checker
+ */
+ void onInlineCompletion(IInlineCompletionRequest request,
+ IInlineCompletionResponse response,
+ CancelChecker cancelChecker);
+}
\ No newline at end of file
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/inlinecompletion/IInlineCompletionRequest.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/inlinecompletion/IInlineCompletionRequest.java
new file mode 100644
index 000000000..3f415b165
--- /dev/null
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/inlinecompletion/IInlineCompletionRequest.java
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2026 Red Hat Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Red Hat Inc. - initial API and implementation
+ */
+package org.eclipse.lemminx.services.extensions.inlinecompletion;
+
+import org.eclipse.lemminx.services.extensions.IPositionRequest;
+import org.eclipse.lemminx.services.extensions.ISharedSettingsRequest;
+import org.eclipse.lsp4j.InlineCompletionContext;
+
+/**
+ * Inline completion request API.
+ */
+public interface IInlineCompletionRequest extends IPositionRequest, ISharedSettingsRequest {
+
+ /**
+ * Returns the inline completion context.
+ *
+ * @return the inline completion context
+ */
+ InlineCompletionContext getContext();
+}
\ No newline at end of file
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/inlinecompletion/IInlineCompletionResponse.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/inlinecompletion/IInlineCompletionResponse.java
new file mode 100644
index 000000000..f612ba035
--- /dev/null
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/inlinecompletion/IInlineCompletionResponse.java
@@ -0,0 +1,34 @@
+/**
+ * Copyright (c) 2026 Red Hat Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Red Hat Inc. - initial API and implementation
+ */
+package org.eclipse.lemminx.services.extensions.inlinecompletion;
+
+import org.eclipse.lsp4j.InlineCompletionItem;
+
+/**
+ * Inline completion response API.
+ *
+ *
+ * This interface provides methods to add inline completion items to the response.
+ * It follows the same pattern as {@link org.eclipse.lemminx.services.extensions.completion.ICompletionResponse}
+ * to maintain API consistency.
+ *
+ */
+public interface IInlineCompletionResponse {
+
+ /**
+ * Add an inline completion item to the response.
+ *
+ * @param item the inline completion item to add
+ */
+ void addInlineCompletionItem(InlineCompletionItem item);
+}
\ No newline at end of file
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/capabilities/ClientCapabilitiesWrapper.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/capabilities/ClientCapabilitiesWrapper.java
index 8b99542c3..e84dadc19 100644
--- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/capabilities/ClientCapabilitiesWrapper.java
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/capabilities/ClientCapabilitiesWrapper.java
@@ -1,5 +1,5 @@
/**
- * Copyright (c) 2018 Red Hat, Inc. and others.
+ * Copyright (c) 2018, 2026 Red Hat, Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
@@ -127,6 +127,10 @@ public boolean isLinkedEditingRangeDynamicRegistered() {
return v3Supported && isDynamicRegistrationSupported(getTextDocument().getLinkedEditingRange());
}
+ public boolean isInlineCompletionDynamicRegistered() {
+ return v3Supported && isDynamicRegistrationSupported(getTextDocument().getInlineCompletion());
+ }
+
private boolean isDynamicRegistrationSupported(DynamicRegistrationCapabilities capability) {
return capability != null && capability.getDynamicRegistration() != null
&& capability.getDynamicRegistration().booleanValue();
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/capabilities/ServerCapabilitiesConstants.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/capabilities/ServerCapabilitiesConstants.java
index bbbef95e1..956694ad6 100644
--- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/capabilities/ServerCapabilitiesConstants.java
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/capabilities/ServerCapabilitiesConstants.java
@@ -1,5 +1,5 @@
/**
- * Copyright (c) 2018 Red Hat, Inc. and others.
+ * Copyright (c) 2018, 2026 Red Hat, Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
@@ -52,6 +52,7 @@ private ServerCapabilitiesConstants() {
public static final String TEXT_DOCUMENT_LINKED_EDITING_RANGE = "textDocument/linkedEditingRange";
public static final String TEXT_DOCUMENT_HIGHLIGHT = "textDocument/documentHighlight";
public static final String TEXT_DOCUMENT_SELECTION_RANGE = "textDocument/selectionRange";
+ public static final String TEXT_DOCUMENT_INLINE_COMPLETION = "textDocument/inlineCompletion";
public static final String WORKSPACE_CHANGE_FOLDERS = "workspace/didChangeWorkspaceFolders";
public static final String WORKSPACE_EXECUTE_COMMAND = "workspace/executeCommand";
@@ -82,6 +83,7 @@ private ServerCapabilitiesConstants() {
public static final String WORKSPACE_CHANGE_FOLDERS_ID = UUID.randomUUID().toString();
public static final String WORKSPACE_WATCHED_FILES_ID = UUID.randomUUID().toString();
public static final String LINKED_EDITING_RANGE_ID = UUID.randomUUID().toString();
+ public static final String INLINE_COMPLETION_ID = UUID.randomUUID().toString();
public static final CompletionOptions DEFAULT_COMPLETION_OPTIONS = new CompletionOptions(true,
Arrays.asList(".", ":", "<", "\"", "=", "/", "\\", "?", "\'", "&", "#"));
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/capabilities/ServerCapabilitiesInitializer.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/capabilities/ServerCapabilitiesInitializer.java
index 79652991c..5aacdf070 100644
--- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/capabilities/ServerCapabilitiesInitializer.java
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/capabilities/ServerCapabilitiesInitializer.java
@@ -1,5 +1,5 @@
/**
- * Copyright (c) 2018, 2023 Red Hat, Inc. and others.
+ * Copyright (c) 2018, 2026 Red Hat, Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
@@ -61,6 +61,7 @@ public static ServerCapabilities getNonDynamicServerCapabilities(ClientCapabilit
serverCapabilities.setLinkedEditingRangeProvider(!clientCapabilities.isLinkedEditingRangeDynamicRegistered());
serverCapabilities.setColorProvider(!clientCapabilities.isColorDynamicRegistrationSupported());
serverCapabilities.setSelectionRangeProvider(!clientCapabilities.isSelectionRangeDynamicRegistered());
+ serverCapabilities.setInlineCompletionProvider(!clientCapabilities.isInlineCompletionDynamicRegistered());
if (clientCapabilities.isWorkspaceFoldersSupported()) {
WorkspaceFoldersOptions workspaceFolders = new WorkspaceFoldersOptions();
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/capabilities/XMLCapabilityManager.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/capabilities/XMLCapabilityManager.java
index 6fc5b454c..684c2f430 100644
--- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/capabilities/XMLCapabilityManager.java
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/capabilities/XMLCapabilityManager.java
@@ -1,5 +1,5 @@
/**
- * Copyright (c) 2018 Red Hat, Inc. and others.
+ * Copyright (c) 2018, 2026 Red Hat, Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
@@ -28,6 +28,7 @@
import static org.eclipse.lemminx.settings.capabilities.ServerCapabilitiesConstants.FORMATTING_ID;
import static org.eclipse.lemminx.settings.capabilities.ServerCapabilitiesConstants.FORMATTING_RANGE_ID;
import static org.eclipse.lemminx.settings.capabilities.ServerCapabilitiesConstants.HOVER_ID;
+import static org.eclipse.lemminx.settings.capabilities.ServerCapabilitiesConstants.INLINE_COMPLETION_ID;
import static org.eclipse.lemminx.settings.capabilities.ServerCapabilitiesConstants.LINKED_EDITING_RANGE_ID;
import static org.eclipse.lemminx.settings.capabilities.ServerCapabilitiesConstants.LINK_ID;
import static org.eclipse.lemminx.settings.capabilities.ServerCapabilitiesConstants.REFERENCES_ID;
@@ -43,6 +44,7 @@
import static org.eclipse.lemminx.settings.capabilities.ServerCapabilitiesConstants.TEXT_DOCUMENT_HIGHLIGHT;
import static org.eclipse.lemminx.settings.capabilities.ServerCapabilitiesConstants.TEXT_DOCUMENT_HOVER;
import static org.eclipse.lemminx.settings.capabilities.ServerCapabilitiesConstants.TEXT_DOCUMENT_LINK;
+import static org.eclipse.lemminx.settings.capabilities.ServerCapabilitiesConstants.TEXT_DOCUMENT_INLINE_COMPLETION;
import static org.eclipse.lemminx.settings.capabilities.ServerCapabilitiesConstants.TEXT_DOCUMENT_LINKED_EDITING_RANGE;
import static org.eclipse.lemminx.settings.capabilities.ServerCapabilitiesConstants.TEXT_DOCUMENT_REFERENCES;
import static org.eclipse.lemminx.settings.capabilities.ServerCapabilitiesConstants.TEXT_DOCUMENT_RENAME;
@@ -185,6 +187,9 @@ public void initializeCapabilities() {
if (this.getClientCapabilities().isLinkedEditingRangeDynamicRegistered()) {
registerCapability(LINKED_EDITING_RANGE_ID, TEXT_DOCUMENT_LINKED_EDITING_RANGE);
}
+ if (this.getClientCapabilities().isInlineCompletionDynamicRegistered()) {
+ registerCapability(INLINE_COMPLETION_ID, TEXT_DOCUMENT_INLINE_COMPLETION);
+ }
if (this.getClientCapabilities().isDidChangeWatchedFilesRegistered()) {
registerWatchedFiles();
}
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/XMLPositionUtility.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/XMLPositionUtility.java
index 39e315a04..58c280ac0 100644
--- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/XMLPositionUtility.java
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/XMLPositionUtility.java
@@ -1,5 +1,5 @@
/**
- * Copyright (c) 2018 Angelo ZERR
+ * Copyright (c) 2018, 2026 Angelo ZERR
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
@@ -411,7 +411,40 @@ private static DOMNode findUnclosedChildNode(String childTag, List chil
}
/**
- * Returns the range of the root start tag (excludes the '<') of the given
+ * Finds the nearest parent element that is not closed at the given offset.
+ *
+ * @param node the starting node
+ * @param offset the current cursor offset
+ * @return the unclosed parent element, or null if none found
+ */
+ public static DOMElement findUnclosedParentElement(DOMNode node, int offset) {
+ DOMNode current = node;
+
+ while (current != null) {
+ if (current.isElement()) {
+ DOMElement element = (DOMElement) current;
+ // Check if the element is not self-closed and not fully closed
+ if (!element.isSelfClosed() && !element.isClosed()) {
+ // Check if we're after the start tag but before any end tag
+ int startTagEnd = element.getStartTagCloseOffset();
+ int endTagStart = element.hasEndTag() ? element.getEndTagOpenOffset() : -1;
+
+ // If we're after the start tag close and either there's no end tag or we're
+ // before it
+ if (startTagEnd != -1 && offset > startTagEnd
+ && (endTagStart == -1 || offset < endTagStart)) {
+ return element;
+ }
+ }
+ }
+ current = current.getParentNode();
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the range of the root start tag (excludes the '<') of the given
* document and null otherwise.
*
* @param document the DOM document.
diff --git a/org.eclipse.lemminx/src/main/resources/META-INF/services/org.eclipse.lemminx.services.extensions.IXMLExtension b/org.eclipse.lemminx/src/main/resources/META-INF/services/org.eclipse.lemminx.services.extensions.IXMLExtension
index 47ec500ac..d45f186fd 100644
--- a/org.eclipse.lemminx/src/main/resources/META-INF/services/org.eclipse.lemminx.services.extensions.IXMLExtension
+++ b/org.eclipse.lemminx/src/main/resources/META-INF/services/org.eclipse.lemminx.services.extensions.IXMLExtension
@@ -1,3 +1,4 @@
+org.eclipse.lemminx.extensions.core.XMLCorePlugin
org.eclipse.lemminx.extensions.contentmodel.ContentModelPlugin
org.eclipse.lemminx.extensions.references.XMLReferencesPlugin
org.eclipse.lemminx.extensions.xsd.XSDPlugin
diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/XMLAssert.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/XMLAssert.java
index b40f5722b..2ef54127c 100644
--- a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/XMLAssert.java
+++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/XMLAssert.java
@@ -103,6 +103,10 @@
import org.eclipse.lsp4j.SelectionRange;
import org.eclipse.lsp4j.SymbolInformation;
import org.eclipse.lsp4j.SymbolKind;
+import org.eclipse.lsp4j.InlineCompletionContext;
+import org.eclipse.lsp4j.InlineCompletionItem;
+import org.eclipse.lsp4j.InlineCompletionList;
+import org.eclipse.lsp4j.InlineCompletionTriggerKind;
import org.eclipse.lsp4j.SnippetTextEdit;
import org.eclipse.lsp4j.TextDocumentEdit;
import org.eclipse.lsp4j.TextDocumentIdentifier;
@@ -2126,4 +2130,92 @@ public static void assertColorPresentation(List extends ColorPresentation> act
public static ColorPresentation colorPres(String label, TextEdit textEdit) {
return new ColorPresentation(label, textEdit);
}
+
+ // ------------------- Inline Completion assert
+
+ // ------------------- Inline Completion assert
+
+ /**
+ * Test inline completion with default language service, expecting specific insert texts
+ */
+ public static void testInlineCompletionFor(String xml, String... expectedInsertTexts) throws BadLocationException {
+ testInlineCompletionFor(new XMLLanguageService(), xml, null, null, expectedInsertTexts);
+ }
+
+ /**
+ * Test inline completion with default language service, expecting a specific count
+ */
+ public static void testInlineCompletionFor(String xml, int expectedCount) throws BadLocationException {
+ testInlineCompletionFor(new XMLLanguageService(), xml, null, expectedCount);
+ }
+
+ /**
+ * Test inline completion with custom language service, expecting specific insert texts
+ */
+ public static void testInlineCompletionFor(XMLLanguageService xmlLanguageService, String xml, String fileURI,
+ String... expectedInsertTexts) throws BadLocationException {
+ testInlineCompletionFor(xmlLanguageService, xml, fileURI, null, expectedInsertTexts);
+ }
+
+ /**
+ * Test inline completion with custom language service, expecting a specific count
+ */
+ public static void testInlineCompletionFor(XMLLanguageService xmlLanguageService, String xml, String fileURI,
+ int expectedCount) throws BadLocationException {
+ testInlineCompletionFor(xmlLanguageService, xml, fileURI, expectedCount, (String[]) null);
+ }
+
+ /**
+ * Test inline completion with custom language service, expecting both count and insert texts
+ */
+ public static void testInlineCompletionFor(XMLLanguageService xmlLanguageService, String xml, String fileURI,
+ Integer expectedCount, String... expectedInsertTexts) throws BadLocationException {
+ int offset = xml.indexOf('|');
+ xml = xml.substring(0, offset) + xml.substring(offset + 1);
+
+ TextDocument document = new TextDocument(xml, fileURI != null ? fileURI : "test://test/test.xml");
+ Position position = document.positionAt(offset);
+ DOMDocument xmlDoc = DOMParser.getInstance().parse(document, xmlLanguageService.getResolverExtensionManager());
+ xmlLanguageService.setDocumentProvider((uri) -> xmlDoc);
+
+ InlineCompletionContext context = new InlineCompletionContext();
+ context.setTriggerKind(InlineCompletionTriggerKind.Invoked);
+
+ SharedSettings settings = new SharedSettings();
+ InlineCompletionList list = xmlLanguageService.doInlineCompletion(xmlDoc, position, context,
+ settings, NULL_CHECKER);
+
+ if (expectedCount != null) {
+ assertEquals(expectedCount.intValue(), list.getItems().size());
+ }
+ if (expectedInsertTexts != null && expectedInsertTexts.length > 0) {
+ for (String expectedText : expectedInsertTexts) {
+ assertInlineCompletion(list, expectedText);
+ }
+ }
+ }
+
+ public static void assertInlineCompletion(InlineCompletionList completions,
+ String expectedInsertText) {
+ List matches = completions.getItems().stream()
+ .filter(item -> {
+ String insertText = getInlineCompletionInsertText(item);
+ return expectedInsertText.equals(insertText);
+ })
+ .collect(Collectors.toList());
+
+ assertTrue(matches.size() > 0,
+ "No inline completion item found with insert text: " + expectedInsertText);
+ }
+
+ private static String getInlineCompletionInsertText(InlineCompletionItem item) {
+ if (item.getInsertText() == null) {
+ return null;
+ }
+ if (item.getInsertText().isLeft()) {
+ return item.getInsertText().getLeft();
+ } else {
+ return item.getInsertText().getRight().getValue();
+ }
+ }
}
\ No newline at end of file
diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/core/XMLCloseTagInlineCompletionTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/core/XMLCloseTagInlineCompletionTest.java
new file mode 100644
index 000000000..07a54b1c1
--- /dev/null
+++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/core/XMLCloseTagInlineCompletionTest.java
@@ -0,0 +1,74 @@
+/**
+ * Copyright (c) 2026 Red Hat Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Red Hat Inc. - initial API and implementation
+ */
+package org.eclipse.lemminx.extensions.core;
+
+import static org.eclipse.lemminx.XMLAssert.testInlineCompletionFor;
+
+import org.eclipse.lemminx.commons.BadLocationException;
+import org.junit.jupiter.api.Test;
+
+/**
+ * XML inline completion tests for closing tags.
+ *
+ * @see org.eclipse.lemminx.extensions.core.participants.inlinecompletion.XMLCloseTagInlineCompletionParticipant
+ */
+public class XMLCloseTagInlineCompletionTest {
+
+ @Test
+ public void testCloseRootElement() throws BadLocationException {
+ String xml = "|";
+ testInlineCompletionFor(xml, "");
+ }
+
+ @Test
+ public void testCloseNestedElement() throws BadLocationException {
+ String xml = "|";
+ testInlineCompletionFor(xml, "");
+ }
+
+ @Test
+ public void testCloseElementWithAttributes() throws BadLocationException {
+ String xml = "|";
+ testInlineCompletionFor(xml, "");
+ }
+
+ @Test
+ public void testNoSuggestionForClosedElement() throws BadLocationException {
+ String xml = "|";
+ testInlineCompletionFor(xml, 0);
+ }
+
+ @Test
+ public void testNoSuggestionForSelfClosedElement() throws BadLocationException {
+ String xml = "|";
+ testInlineCompletionFor(xml, 0);
+ }
+
+ @Test
+ public void testCloseMultipleNestedElements() throws BadLocationException {
+ String xml = "|";
+ testInlineCompletionFor(xml, "");
+ }
+
+ @Test
+ public void testCloseAfterText() throws BadLocationException {
+ String xml = "text|";
+ testInlineCompletionFor(xml, "");
+ }
+
+ @Test
+ public void testCloseAfterWhitespace() throws BadLocationException {
+ String xml = " |";
+ testInlineCompletionFor(xml, "");
+ }
+}
\ No newline at end of file
diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/XMLInlineCompletionTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/XMLInlineCompletionTest.java
new file mode 100644
index 000000000..f14b6cbc0
--- /dev/null
+++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/XMLInlineCompletionTest.java
@@ -0,0 +1,161 @@
+/**
+ * Copyright (c) 2026 Red Hat Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Red Hat Inc. - initial API and implementation
+ */
+package org.eclipse.lemminx.services;
+
+import static org.eclipse.lemminx.XMLAssert.testInlineCompletionFor;
+
+import org.eclipse.lemminx.commons.BadLocationException;
+import org.eclipse.lemminx.services.extensions.inlinecompletion.IInlineCompletionParticipant;
+import org.eclipse.lemminx.services.extensions.inlinecompletion.IInlineCompletionRequest;
+import org.eclipse.lemminx.services.extensions.inlinecompletion.IInlineCompletionResponse;
+import org.eclipse.lsp4j.InlineCompletionItem;
+import org.eclipse.lsp4j.jsonrpc.CancelChecker;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * XML inline completion service tests with custom participants.
+ */
+public class XMLInlineCompletionTest {
+
+ private XMLLanguageService languageService;
+
+ @BeforeEach
+ public void initializeLanguageService() {
+ languageService = new XMLLanguageService();
+ }
+
+ @Test
+ public void testInlineCompletionWithNoParticipants() throws BadLocationException {
+ String xml = "|";
+ testInlineCompletionFor(languageService, xml, null, 0);
+ }
+
+ @Test
+ public void testInlineCompletionWithCustomParticipant() throws BadLocationException {
+ // Register a custom participant that always suggests "test"
+ languageService.registerInlineCompletionParticipant(
+ new IInlineCompletionParticipant() {
+ @Override
+ public void onInlineCompletion(IInlineCompletionRequest request, IInlineCompletionResponse response,
+ CancelChecker cancelChecker) {
+ InlineCompletionItem item = new InlineCompletionItem();
+ item.setInsertText("test");
+ response.addInlineCompletionItem(item);
+ }
+ }
+ );
+
+ String xml = "|";
+ testInlineCompletionFor(languageService, xml, "test");
+ }
+
+ @Test
+ public void testInlineCompletionWithMultipleParticipants() throws BadLocationException {
+ // Register multiple participants
+ languageService.registerInlineCompletionParticipant(
+ new IInlineCompletionParticipant() {
+ @Override
+ public void onInlineCompletion(IInlineCompletionRequest request, IInlineCompletionResponse response,
+ CancelChecker cancelChecker) {
+ InlineCompletionItem item = new InlineCompletionItem();
+ item.setInsertText("suggestion1");
+ response.addInlineCompletionItem(item);
+ }
+ }
+ );
+
+ languageService.registerInlineCompletionParticipant(
+ new IInlineCompletionParticipant() {
+ @Override
+ public void onInlineCompletion(IInlineCompletionRequest request, IInlineCompletionResponse response,
+ CancelChecker cancelChecker) {
+ InlineCompletionItem item = new InlineCompletionItem();
+ item.setInsertText("suggestion2");
+ response.addInlineCompletionItem(item);
+ }
+ }
+ );
+
+ String xml = "|";
+ testInlineCompletionFor(languageService, xml, "suggestion1", "suggestion2");
+ }
+
+ @Test
+ public void testInlineCompletionContext() throws BadLocationException {
+ // Register a participant that checks the context
+ languageService.registerInlineCompletionParticipant(
+ new IInlineCompletionParticipant() {
+ @Override
+ public void onInlineCompletion(IInlineCompletionRequest request, IInlineCompletionResponse response,
+ CancelChecker cancelChecker) {
+ InlineCompletionItem item = new InlineCompletionItem();
+ item.setInsertText("context-aware");
+ response.addInlineCompletionItem(item);
+ }
+ }
+ );
+
+ String xml = "|";
+ testInlineCompletionFor(languageService, xml, "context-aware");
+ }
+
+ @Test
+ public void testInlineCompletionAtDifferentPositions() throws BadLocationException {
+ languageService.registerInlineCompletionParticipant(
+ new IInlineCompletionParticipant() {
+ @Override
+ public void onInlineCompletion(IInlineCompletionRequest request, IInlineCompletionResponse response,
+ CancelChecker cancelChecker) {
+ int offset = request.getOffset();
+ InlineCompletionItem item = new InlineCompletionItem();
+ item.setInsertText("offset:" + offset);
+ response.addInlineCompletionItem(item);
+ }
+ }
+ );
+
+ // Test at start of content
+ String xml1 = "|";
+ testInlineCompletionFor(languageService, xml1, "offset:6");
+
+ // Test at end of content
+ String xml2 = "content|";
+ testInlineCompletionFor(languageService, xml2, "offset:13");
+ }
+
+ @Test
+ public void testInlineCompletionWithEmptyDocument() throws BadLocationException {
+ String xml = "|";
+ testInlineCompletionFor(languageService, xml, null, 0);
+ }
+
+ @Test
+ public void testInlineCompletionWithNestedElements() throws BadLocationException {
+ languageService.registerInlineCompletionParticipant(
+ new IInlineCompletionParticipant() {
+ @Override
+ public void onInlineCompletion(IInlineCompletionRequest request, IInlineCompletionResponse response,
+ CancelChecker cancelChecker) {
+ InlineCompletionItem item = new InlineCompletionItem();
+ item.setInsertText("nested");
+ response.addInlineCompletionItem(item);
+ }
+ }
+ );
+
+ String xml = "|";
+ testInlineCompletionFor(languageService, xml, "nested");
+ }
+
+}
\ No newline at end of file
diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/utils/XMLPositionUtilityTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/utils/XMLPositionUtilityTest.java
index 96cffd1f4..51cd3895b 100644
--- a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/utils/XMLPositionUtilityTest.java
+++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/utils/XMLPositionUtilityTest.java
@@ -1,5 +1,5 @@
/*******************************************************************************
-* Copyright (c) 2019 Red Hat Inc. and others.
+* Copyright (c) 2019, 2026 Red Hat Inc. and others.
* All rights reserved. This program and the accompanying materials
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v20.html
@@ -201,4 +201,92 @@ private static void testMatchingTagPosition(String initialCursorText, String exp
assertEquals(expectedCursorText, actualOutputString);
}
+
+ @Test
+ public void testFindUnclosedParentElementSimple() {
+ String xml = "|";
+ testFindUnclosedParentElement(xml, "root");
+ }
+
+ @Test
+ public void testFindUnclosedParentElementWithContent() {
+ String xml = "content|";
+ testFindUnclosedParentElement(xml, "root");
+ }
+
+ @Test
+ public void testFindUnclosedParentElementNested() {
+ String xml = "|";
+ testFindUnclosedParentElement(xml, "child");
+ }
+
+ @Test
+ public void testFindUnclosedParentElementClosed() {
+ String xml = "|";
+ testFindUnclosedParentElement(xml, null);
+ }
+
+ @Test
+ public void testFindUnclosedParentElementSelfClosed() {
+ String xml = "|";
+ testFindUnclosedParentElement(xml, null);
+ }
+
+ @Test
+ public void testFindUnclosedParentElementWithAttributes() {
+ String xml = "|";
+ testFindUnclosedParentElement(xml, "root");
+ }
+
+ @Test
+ public void testFindUnclosedParentElementBeforeStartTag() {
+ String xml = "|";
+ testFindUnclosedParentElement(xml, null);
+ }
+
+ @Test
+ public void testFindUnclosedParentElementInStartTag() {
+ String xml = "";
+ testFindUnclosedParentElement(xml, null);
+ }
+
+ @Test
+ public void testFindUnclosedParentElementMultipleNested() {
+ String xml = "|";
+ testFindUnclosedParentElement(xml, "child");
+ }
+
+ @Test
+ public void testFindUnclosedParentElementAfterClosedChild() {
+ String xml = "|";
+ testFindUnclosedParentElement(xml, "root");
+ }
+
+ /**
+ * Test findUnclosedParentElement for the given XML content.
+ * The '|' character marks the cursor position.
+ *
+ * @param xml the XML content with cursor position marked by '|'
+ * @param expectedTagName the expected tag name of the unclosed parent element, or null if none expected
+ */
+ private static void testFindUnclosedParentElement(String xml, String expectedTagName) {
+ int offset = xml.indexOf('|');
+ if (offset == -1) {
+ fail("XML must contain '|' to mark cursor position");
+ }
+
+ String xmlWithoutCursor = xml.substring(0, offset) + xml.substring(offset + 1);
+ DOMDocument document = DOMParser.getInstance().parse(xmlWithoutCursor, "test.xml", null);
+
+ var node = document.findNodeAt(offset);
+ var unclosedElement = XMLPositionUtility.findUnclosedParentElement(node, offset);
+
+ if (expectedTagName == null) {
+ Assertions.assertNull(unclosedElement, "Expected no unclosed parent element");
+ } else {
+ Assertions.assertNotNull(unclosedElement, "Expected to find unclosed parent element");
+ Assertions.assertEquals(expectedTagName, unclosedElement.getTagName(),
+ "Expected unclosed parent element tag name to be '" + expectedTagName + "'");
+ }
+ }
}
\ No newline at end of file