Skip to content

Commit 157fa9b

Browse files
committed
XML Minify support
Fixes redhat-developer/vscode-xml#609 Signed-off-by: azerr <azerr@redhat.com>
1 parent 5ec1767 commit 157fa9b

9 files changed

Lines changed: 802 additions & 2 deletions

File tree

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2026 Red Hat Inc. and others.
3+
* All rights reserved. This program and the accompanying materials
4+
* which accompanies this distribution, and is available at
5+
* http://www.eclipse.org/legal/epl-v20.html
6+
*
7+
* SPDX-License-Identifier: EPL-2.0
8+
*
9+
* Contributors:
10+
* Red Hat Inc. - initial API and implementation
11+
*******************************************************************************/
12+
package org.eclipse.lemminx.extensions.minify;
13+
14+
import org.eclipse.lemminx.services.IXMLDocumentProvider;
15+
import org.eclipse.lemminx.services.commands.MinifyDocumentCommand;
16+
import org.eclipse.lemminx.services.extensions.IXMLExtension;
17+
import org.eclipse.lemminx.services.extensions.XMLExtensionsRegistry;
18+
import org.eclipse.lemminx.services.extensions.commands.IXMLCommandService;
19+
import org.eclipse.lemminx.services.extensions.save.ISaveContext;
20+
import org.eclipse.lsp4j.InitializeParams;
21+
22+
/**
23+
* Minify plugin to register the minify command.
24+
*/
25+
public class MinifyPlugin implements IXMLExtension {
26+
27+
@Override
28+
public void start(InitializeParams params, XMLExtensionsRegistry registry) {
29+
// Register minify command
30+
IXMLCommandService commandService = registry.getCommandService();
31+
if (commandService != null) {
32+
IXMLDocumentProvider documentProvider = registry.getDocumentProvider();
33+
commandService.registerCommand(MinifyDocumentCommand.COMMAND_ID,
34+
new MinifyDocumentCommand(documentProvider));
35+
}
36+
}
37+
38+
@Override
39+
public void stop(XMLExtensionsRegistry registry) {
40+
// Unregister minify command
41+
IXMLCommandService commandService = registry.getCommandService();
42+
if (commandService != null) {
43+
commandService.unregisterCommand(MinifyDocumentCommand.COMMAND_ID);
44+
}
45+
}
46+
47+
@Override
48+
public void doSave(ISaveContext context) {
49+
// Nothing to do on save
50+
}
51+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2026 Red Hat Inc. and others.
3+
* All rights reserved. This program and the accompanying materials
4+
* which accompanies this distribution, and is available at
5+
* http://www.eclipse.org/legal/epl-v20.html
6+
*
7+
* SPDX-License-Identifier: EPL-2.0
8+
*
9+
* Contributors:
10+
* Red Hat Inc. - initial API and implementation
11+
*******************************************************************************/
12+
package org.eclipse.lemminx.services;
13+
14+
import org.eclipse.lemminx.settings.SharedSettings;
15+
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
16+
17+
/**
18+
* XML minifier API.
19+
*
20+
*/
21+
public interface IXMLMinifier {
22+
23+
/**
24+
* Minify the given text document.
25+
*
26+
* @param text the text.
27+
* @param uri the uri.
28+
* @param sharedSettings the shared settings.
29+
* @param cancelChecker the cancel checker.
30+
*
31+
* @return the minified text document.
32+
*/
33+
String minify(String text, String uri, SharedSettings sharedSettings, CancelChecker cancelChecker);
34+
}

org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLLanguageService.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@
6767
* XML Language service.
6868
*
6969
*/
70-
public class XMLLanguageService extends XMLExtensionsRegistry implements IXMLFullFormatter {
70+
public class XMLLanguageService extends XMLExtensionsRegistry implements IXMLFullFormatter, IXMLMinifier {
7171

7272
private static final CancelChecker NULL_CHECKER = new CancelChecker() {
7373

@@ -78,6 +78,7 @@ public void checkCanceled() {
7878
};
7979

8080
private final XMLFormatter formatter;
81+
private final XMLMinifier minifier;
8182
private final XMLHighlighting highlighting;
8283
private final XMLSymbolsProvider symbolsProvider;
8384
private final XMLCompletions completions;
@@ -98,6 +99,7 @@ public void checkCanceled() {
9899

99100
public XMLLanguageService() {
100101
this.formatter = new XMLFormatter(this);
102+
this.minifier = new XMLMinifier();
101103
this.highlighting = new XMLHighlighting(this);
102104
this.symbolsProvider = new XMLSymbolsProvider(this);
103105
this.completions = new XMLCompletions(this);
@@ -135,6 +137,24 @@ public List<? extends TextEdit> format(DOMDocument xmlDocument, Range range, Sha
135137
return formatter.format(xmlDocument, range, sharedSettings);
136138
}
137139

140+
@Override
141+
public String minify(String text, String uri, SharedSettings sharedSettings, CancelChecker cancelChecker) {
142+
DOMDocument xmlDocument = DOMParser.getInstance().parse(new TextDocument(text, uri), null);
143+
List<? extends TextEdit> edits = this.minify(xmlDocument, null, sharedSettings);
144+
try {
145+
return TextEditUtils.applyEdits(xmlDocument.getTextDocument(), edits);
146+
} catch (Exception e) {
147+
if (edits != null && edits.size() == 1) {
148+
return edits.get(0).getNewText();
149+
}
150+
return text;
151+
}
152+
}
153+
154+
public List<? extends TextEdit> minify(DOMDocument xmlDocument, Range range, SharedSettings sharedSettings) {
155+
return minifier.minify(xmlDocument, range, sharedSettings);
156+
}
157+
138158
public List<DocumentHighlight> findDocumentHighlights(DOMDocument xmlDocument, Position position) {
139159
return findDocumentHighlights(xmlDocument, position, NULL_CHECKER);
140160
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/**
2+
* Copyright (c) 2026 Angelo ZERR
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v2.0
5+
* which accompanies this distribution, and is available at
6+
* http://www.eclipse.org/legal/epl-v20.html
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*
10+
* Contributors:
11+
* Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
12+
*/
13+
package org.eclipse.lemminx.services;
14+
15+
import static org.eclipse.lemminx.utils.TextEditUtils.applyEdits;
16+
17+
import java.util.List;
18+
import java.util.logging.Level;
19+
import java.util.logging.Logger;
20+
21+
import org.eclipse.lemminx.commons.BadLocationException;
22+
import org.eclipse.lemminx.commons.TextDocument;
23+
import org.eclipse.lemminx.dom.DOMDocument;
24+
import org.eclipse.lemminx.services.minify.XMLMinifierDocument;
25+
import org.eclipse.lemminx.settings.SharedSettings;
26+
import org.eclipse.lsp4j.Position;
27+
import org.eclipse.lsp4j.Range;
28+
import org.eclipse.lsp4j.TextEdit;
29+
30+
/**
31+
* XML minifier support.
32+
*
33+
*/
34+
public class XMLMinifier {
35+
private static final Logger LOGGER = Logger.getLogger(XMLMinifier.class.getName());
36+
37+
public XMLMinifier() {
38+
}
39+
40+
/**
41+
* Returns a List containing multiple TextEdits to remove spaces.
42+
*
43+
* @param xmlDocument document to perform minification on
44+
* @param range specified range in which minification will be done
45+
* @param sharedSettings settings containing minification preferences
46+
* @return List containing TextEdit with minification changes
47+
*/
48+
public List<? extends TextEdit> minify(DOMDocument xmlDocument, Range range, SharedSettings sharedSettings) {
49+
try {
50+
XMLMinifierDocument minifierDocument = new XMLMinifierDocument(xmlDocument, range, sharedSettings);
51+
List<? extends TextEdit> result = minifierDocument.minify();
52+
53+
// For large files, merge all TextEdits into a single one to avoid OutOfMemory
54+
// This is more memory efficient as we don't keep thousands of TextEdit objects
55+
if (shouldMergeEdits(result, xmlDocument)) {
56+
String minified = applyEdits(xmlDocument.getTextDocument(), result);
57+
Range editRange = range != null ? range : getFullDocumentRange(xmlDocument);
58+
return List.of(new TextEdit(editRange, minified));
59+
}
60+
return result;
61+
} catch (BadLocationException e) {
62+
LOGGER.log(Level.SEVERE, "Minification failed due to BadLocation", e);
63+
}
64+
return null;
65+
}
66+
67+
/**
68+
* Determines if TextEdits should be merged into a single edit.
69+
* Merging is beneficial for large files to reduce memory consumption.
70+
*
71+
* @param edits the list of text edits
72+
* @param xmlDocument the XML document
73+
* @return true if edits should be merged
74+
*/
75+
private boolean shouldMergeEdits(List<? extends TextEdit> edits, DOMDocument xmlDocument) {
76+
// Merge if there are many edits (> 1000) or if the document is large (> 100KB)
77+
int editCount = edits != null ? edits.size() : 0;
78+
int documentSize = xmlDocument.getTextDocument().getText().length();
79+
return editCount > 1000 || documentSize > 100_000;
80+
}
81+
82+
/**
83+
* Returns the full document range.
84+
*
85+
* @param xmlDocument the XML document
86+
* @return the full document range
87+
* @throws BadLocationException if position calculation fails
88+
*/
89+
private Range getFullDocumentRange(DOMDocument xmlDocument) throws BadLocationException {
90+
TextDocument textDocument = xmlDocument.getTextDocument();
91+
Position start = new Position(0, 0);
92+
Position end = textDocument.positionAt(textDocument.getText().length());
93+
return new Range(start, end);
94+
}
95+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2026 Red Hat Inc. and others.
3+
* All rights reserved. This program and the accompanying materials
4+
* which accompanies this distribution, and is available at
5+
* http://www.eclipse.org/legal/epl-v20.html
6+
*
7+
* SPDX-License-Identifier: EPL-2.0
8+
*
9+
* Contributors:
10+
* Red Hat Inc. - initial API and implementation
11+
*******************************************************************************/
12+
package org.eclipse.lemminx.services.commands;
13+
14+
import java.util.List;
15+
16+
import org.eclipse.lemminx.dom.DOMDocument;
17+
import org.eclipse.lemminx.services.IXMLDocumentProvider;
18+
import org.eclipse.lemminx.services.XMLMinifier;
19+
import org.eclipse.lemminx.services.extensions.commands.AbstractDOMDocumentCommandHandler;
20+
import org.eclipse.lemminx.settings.SharedSettings;
21+
import org.eclipse.lsp4j.ExecuteCommandParams;
22+
import org.eclipse.lsp4j.TextEdit;
23+
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
24+
25+
/**
26+
* XML Command "xml.minify.document" to minify an XML document by removing
27+
* unnecessary whitespace.
28+
*
29+
*/
30+
public class MinifyDocumentCommand extends AbstractDOMDocumentCommandHandler {
31+
32+
public static final String COMMAND_ID = "xml.minify.document";
33+
34+
private final XMLMinifier minifier;
35+
36+
public MinifyDocumentCommand(IXMLDocumentProvider documentProvider) {
37+
super(documentProvider);
38+
this.minifier = new XMLMinifier();
39+
}
40+
41+
@Override
42+
protected Object executeCommand(DOMDocument document, ExecuteCommandParams params, SharedSettings sharedSettings,
43+
CancelChecker cancelChecker) throws Exception {
44+
// Get the minification text edits
45+
List<? extends TextEdit> edits = minifier.minify(document, null, sharedSettings);
46+
return edits;
47+
}
48+
}

0 commit comments

Comments
 (0)