Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -275,6 +279,16 @@ public CompletableFuture<CompletionItem> resolveCompletionItem(CompletionItem un
});
}

@Override
public CompletableFuture<Either<List<InlineCompletionItem>, 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> hover(HoverParams params) {
return computeDOMAsync(params.getTextDocument(), (xmlDocument, cancelChecker) -> {
Expand Down
Original file line number Diff line number Diff line change
@@ -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.).
*
* <p>
* This plugin provides:
* <ul>
* <li>Inline completion for closing tags</li>
* </ul>
* </p>
*/
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
}
}
Original file line number Diff line number Diff line change
@@ -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);
Comment thread
angelozerr marked this conversation as resolved.
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);
}
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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<InlineCompletionItem> 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<InlineCompletionItem> getItems() {
return items;
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Loading
Loading