Removed usage of LemMinX

Enhanced XML Auto complete
This commit is contained in:
Akash Yadav
2021-10-08 15:27:56 +05:30
parent 1a96d21f14
commit e87a6ec32b
457 changed files with 172 additions and 65139 deletions
+13 -11
View File
@@ -1,7 +1,10 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'com.google.gms.google-services' apply plugin: 'com.google.gms.google-services'
apply plugin: 'com.google.firebase.crashlytics' apply plugin: 'com.google.firebase.crashlytics'
apply plugin: com.github.megatronking.stringfog.plugin.StringFogPlugin
if(rootProject.ext.stringfogEnabled) {
apply plugin: com.github.megatronking.stringfog.plugin.StringFogPlugin
}
android { android {
compileSdkVersion rootProject.ext.compileSdk compileSdkVersion rootProject.ext.compileSdk
@@ -61,13 +64,11 @@ android {
minifyEnabled false minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.debug signingConfig signingConfigs.debug
stringfog.enable false
} }
release { release {
minifyEnabled true minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release signingConfig signingConfigs.release
stringfog.enable true
} }
} }
@@ -79,10 +80,12 @@ android {
} }
} }
stringfog { if(rootProject.ext.stringfogEnabled) {
key 'WVV0aGMwaEFZVzVFVW05SlJFbGtaUT09' stringfog {
enable true key 'WVV0aGMwaEFZVzVFVW05SlJFbGtaUT09'
implementation 'com.github.megatronking.stringfog.xor.StringFogImpl' enable true
implementation 'com.github.megatronking.stringfog.xor.StringFogImpl'
}
} }
task copyBindings(type: Copy) { task copyBindings(type: Copy) {
@@ -114,7 +117,7 @@ dependencies {
implementation 'androidx.annotation:annotation:1.2.0' implementation 'androidx.annotation:annotation:1.2.0'
implementation 'androidx.appcompat:appcompat:1.3.1' implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.1'
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.1.0' implementation 'androidx.coordinatorlayout:coordinatorlayout:1.1.0'
implementation 'androidx.drawerlayout:drawerlayout:1.1.1' implementation 'androidx.drawerlayout:drawerlayout:1.1.1'
implementation 'androidx.gridlayout:gridlayout:1.0.0' implementation 'androidx.gridlayout:gridlayout:1.0.0'
@@ -126,7 +129,7 @@ dependencies {
implementation 'com.google.android.material:material:1.4.0' implementation 'com.google.android.material:material:1.4.0'
// Firebase // Firebase
implementation platform('com.google.firebase:firebase-bom:28.1.0') implementation platform('com.google.firebase:firebase-bom:28.4.2')
implementation 'com.google.firebase:firebase-analytics' implementation 'com.google.firebase:firebase-analytics'
implementation 'com.google.firebase:firebase-crashlytics' implementation 'com.google.firebase:firebase-crashlytics'
implementation 'com.google.firebase:firebase-messaging' implementation 'com.google.firebase:firebase-messaging'
@@ -137,8 +140,7 @@ dependencies {
implementation project(':emulator') implementation project(':emulator')
implementation project(':emulatorview') implementation project(':emulatorview')
implementation project(':javapoet') implementation project(':javapoet')
implementation project(':lsp')
api project(':lemminx')
// LSP4J // LSP4J
implementation 'org.eclipse.lsp4j:org.eclipse.lsp4j:0.12.0' implementation 'org.eclipse.lsp4j:org.eclipse.lsp4j:0.12.0'
@@ -996,10 +996,6 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
Optional<InitializeResult> result = LSP.Java.init(mProject.getProjectPath()); Optional<InitializeResult> result = LSP.Java.init(mProject.getProjectPath());
LSP.Java.initialized(); LSP.Java.initialized();
}); });
LSP.XML.start(() -> {
Optional<InitializeResult> result = LSP.XML.init(mProject.getProjectPath());
LSP.XML.initialized();
});
} }
private void getProjectFromIntent() { private void getProjectFromIntent() {
@@ -285,7 +285,7 @@ public class EditorFragment extends BaseFragment implements EditorEventListener
mBinding.editor.setEditorLanguage(new EmptyLanguage()); mBinding.editor.setEditorLanguage(new EmptyLanguage());
} }
// File must be set only after setting the language server // File must be set only after setting the language
// This will make sure that textDocument/didOpen is sent // This will make sure that textDocument/didOpen is sent
mBinding.editor.setFile(getFile()); mBinding.editor.setFile(getFile());
@@ -16,7 +16,7 @@ public class GroovyAutoComplete implements AutoCompleteProvider {
private static final List<String> OTHERS = createOtherCompletions(); private static final List<String> OTHERS = createOtherCompletions();
@Override @Override
public List<CompletionItem> getAutoCompleteItems(String fileUri, String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int index, int line, int column) throws Exception { public List<CompletionItem> getAutoCompleteItems(CharSequence content, String fileUri, String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int index, int line, int column) throws Exception {
List<CompletionItem> result = new ArrayList<>(); List<CompletionItem> result = new ArrayList<>();
for(String artifact : ANDROIDX_ARTIFACTS) { for(String artifact : ANDROIDX_ARTIFACTS) {
@@ -22,7 +22,7 @@ public class JavaAutoComplete implements AutoCompleteProvider {
private CompletableFuture<Either<List<CompletionItem>, CompletionList>> future; private CompletableFuture<Either<List<CompletionItem>, CompletionList>> future;
@Override @Override
public List<CompletionItem> getAutoCompleteItems(String fileUri, String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int index, int line, int column) throws Exception { public List<CompletionItem> getAutoCompleteItems(CharSequence content, String fileUri, String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int index, int line, int column) throws Exception {
IDELanguageServer languageServer = LSPProvider.getServerForLanguage(LSPProvider.LANGUAGE_JAVA); IDELanguageServer languageServer = LSPProvider.getServerForLanguage(LSPProvider.LANGUAGE_JAVA);
if(languageServer != null && fileUri != null) { if(languageServer != null && fileUri != null) {
@@ -105,7 +105,7 @@ public class LogLanguageImpl extends BaseLanguage {
private static class LogCompletor implements AutoCompleteProvider { private static class LogCompletor implements AutoCompleteProvider {
@Override @Override
public List<CompletionItem> getAutoCompleteItems(String fileUri, String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int index, int line, int column) { public List<CompletionItem> getAutoCompleteItems(CharSequence content, String fileUri, String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int index, int line, int column) {
return new ArrayList<CompletionItem>(); return new ArrayList<CompletionItem>();
} }
} }
@@ -1,72 +1,34 @@
package com.itsaky.androidide.language.xml; package com.itsaky.androidide.language.xml;
import com.itsaky.androidide.lsp.LSPProvider; import com.itsaky.androidide.app.StudioApp;
import com.itsaky.lsp.services.IDELanguageServer; import com.itsaky.androidide.language.xml.completion.XMLCompletionService;
import com.itsaky.androidide.utils.Logger;
import io.github.rosemoe.editor.interfaces.AutoCompleteProvider; import io.github.rosemoe.editor.interfaces.AutoCompleteProvider;
import io.github.rosemoe.editor.text.TextAnalyzeResult; import io.github.rosemoe.editor.text.TextAnalyzeResult;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture;
import org.eclipse.lsp4j.CompletionItem; import org.eclipse.lsp4j.CompletionItem;
import org.eclipse.lsp4j.CompletionList;
import org.eclipse.lsp4j.CompletionParams;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import com.itsaky.androidide.utils.Logger;
public class XMLAutoComplete implements AutoCompleteProvider { public class XMLAutoComplete implements AutoCompleteProvider {
private static final Logger LOG = Logger.instance("XMLAutoComplete"); private static final Logger LOG = Logger.instance("XMLAutoComplete");
private Comparator<CompletionItem> RESULTS_SORTER = new Comparator<CompletionItem>(){ private Comparator<CompletionItem> RESULTS_SORTER = new Comparator<CompletionItem>(){
@Override @Override
public int compare(CompletionItem p1, CompletionItem p2) { public int compare(CompletionItem p1, CompletionItem p2) {
if(p1.getSortText() == null && p1.getSortText() != null) return -1; if(p1.getSortText() == null && p2.getSortText() != null) return -1;
if(p1.getSortText() != null && p1.getSortText() == null) return 1; if(p1.getSortText() != null && p2.getSortText() == null) return 1;
if(p1.getSortText() == null && p1.getSortText() == null) return 0; if(p1.getSortText() == null && p2.getSortText() == null) return 0;
return p1.getSortText().compareTo(p2.getSortText()); return p1.getSortText().compareTo(p2.getSortText());
} }
}; };
private CompletableFuture<Either<List<CompletionItem>, CompletionList>> lastRequest;
/**
* TODO: Change this to a language server implementation and remove the use of passing editor content
*/
@Override @Override
public List<CompletionItem> getAutoCompleteItems(String fileUri, String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int index, int line, int column) { public List<CompletionItem> getAutoCompleteItems(CharSequence content, String fileUri, String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int index, int line, int column) {
final IDELanguageServer server = LSPProvider.getServerForLanguage(LSPProvider.LANGUAGE_XML); final XMLCompletionService service = StudioApp.getInstance().getXmlCompletionService();
if(server == null) return sort(service.complete(content, index, line, column, prefix.toLowerCase().trim()));
return List.of();
if(lastRequest != null && !lastRequest.isDone()) {
lastRequest.cancel(true);
}
final CompletionParams params = new CompletionParams();
params.setTextDocument(new TextDocumentIdentifier(fileUri));
params.setPosition(new Position(line, column));
lastRequest = server.getTextDocumentService().completion(params);
try {
Either<List<CompletionItem>, CompletionList> result = lastRequest.get();
if(result != null) {
if(result.isLeft()) {
return sort(result.getLeft());
} else if(result.isRight()) {
return sort(result.getRight().getItems());
}
}
} catch (Throwable th) {
LOG.error("XML Completion error", th);
return List.of();
}
return List.of();
} }
private List<CompletionItem> sort(List<CompletionItem> result) { private List<CompletionItem> sort(List<CompletionItem> result) {
@@ -14,13 +14,14 @@ import java.io.File;
import java.io.StringReader; import java.io.StringReader;
import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.Token; import org.antlr.v4.runtime.Token;
import com.itsaky.androidide.lsp.LanguageServerWrapper;
public class XMLLanguage extends BaseLanguage { public class XMLLanguage extends BaseLanguage {
private XMLAnalyzer analyzer; private XMLAnalyzer analyzer;
private XMLAutoComplete completer; private XMLAutoComplete completer;
private NewlineHandler[] newlineHandlers = new NewlineHandler[] {}; private NewlineHandler[] newlineHandlers = new NewlineHandler[] {};
public XMLLanguage() { public XMLLanguage() {
super(null); super(null);
} }
@@ -34,7 +35,7 @@ public class XMLLanguage extends BaseLanguage {
@Override @Override
public IDELanguageServer getLanguageServer() { public IDELanguageServer getLanguageServer() {
return LSPProvider.getServerForLanguage(getLanguageCode()); return null;
} }
@Override @Override
@@ -3,7 +3,6 @@ package com.itsaky.androidide.language.xml.completion;
import com.itsaky.androidide.language.xml.lexer.XMLLexer; import com.itsaky.androidide.language.xml.lexer.XMLLexer;
import com.itsaky.androidide.utils.Environment; import com.itsaky.androidide.utils.Environment;
import com.itsaky.androidide.utils.FileUtil; import com.itsaky.androidide.utils.FileUtil;
import io.github.rosemoe.editor.widget.CodeEditor;
import java.io.File; import java.io.File;
import java.io.StringReader; import java.io.StringReader;
import java.util.ArrayList; import java.util.ArrayList;
@@ -17,16 +16,16 @@ import java.util.Set;
import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.Token; import org.antlr.v4.runtime.Token;
import org.eclipse.lsp4j.CompletionItem; import org.eclipse.lsp4j.CompletionItem;
import org.eclipse.lsp4j.CompletionItemKind;
import org.eclipse.lsp4j.InsertTextFormat;
import org.jsoup.Jsoup; import org.jsoup.Jsoup;
import org.jsoup.nodes.Document; import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element; import org.jsoup.nodes.Element;
import org.jsoup.select.Elements; import org.jsoup.select.Elements;
import org.eclipse.lsp4j.InsertTextFormat;
import org.eclipse.lsp4j.CompletionItemKind;
public class XMLCompletionService { public class XMLCompletionService {
private HashMap<String, Attr> attrs; private HashMap<String, Attr> attrs;
private List<String> widgets; private List<Widget> widgets;
private final File ANDROID_ATTRS; private final File ANDROID_ATTRS;
private final File ANDROID_WIDGETS; private final File ANDROID_WIDGETS;
@@ -37,6 +36,7 @@ public class XMLCompletionService {
this.ANDROID_WIDGETS = new File(Environment.BOOTCLASSPATH.getParentFile(), "data/widgets.txt"); this.ANDROID_WIDGETS = new File(Environment.BOOTCLASSPATH.getParentFile(), "data/widgets.txt");
this.attrs = new HashMap<>(); this.attrs = new HashMap<>();
this.widgets = new ArrayList<>(); this.widgets = new ArrayList<>();
if(ANDROID_ATTRS.exists() && ANDROID_ATTRS.isFile()) { if(ANDROID_ATTRS.exists() && ANDROID_ATTRS.isFile()) {
addAttributesFrom(ANDROID_ATTRS, true); addAttributesFrom(ANDROID_ATTRS, true);
} }
@@ -51,8 +51,7 @@ public class XMLCompletionService {
return initiated; return initiated;
} }
public List<CompletionItem> complete(CharSequence content, int index, int line, int column, String prefix) {
public List<CompletionItem> complete(int index, int line, int column, String prefix) {
final List<CompletionItem> result = new ArrayList<>(); final List<CompletionItem> result = new ArrayList<>();
if(prefix.startsWith("<") || prefix.startsWith("</")) { if(prefix.startsWith("<") || prefix.startsWith("</")) {
boolean slash = false; boolean slash = false;
@@ -61,16 +60,12 @@ public class XMLCompletionService {
slash = true; slash = true;
prefix = prefix.substring(1); prefix = prefix.substring(1);
} }
for(String name : widgets) { for(Widget view : widgets) {
if(simpleName(name).toLowerCase(Locale.US).startsWith(prefix)) if(view.simpleName.toLowerCase(Locale.US).startsWith(prefix))
result.add(widgetNameAsCompletion(name, slash)); result.add(widgetNameAsCompletion(view, slash));
} }
return handleResults(result); return handleResults(result);
} else { } else {
/**
* TODO: Change to LSP
*/
final String content = "";
final IsInValueScanner scanner = new IsInValueScanner(index); final IsInValueScanner scanner = new IsInValueScanner(index);
final String name = scanner.scan(content); final String name = scanner.scan(content);
if(name != null) { if(name != null) {
@@ -110,24 +105,64 @@ public class XMLCompletionService {
CompletionItem item = new CompletionItem(); CompletionItem item = new CompletionItem();
item.setLabel(attr.name); item.setLabel(attr.name);
item.setDetail("Attribute"); item.setDetail("Attribute");
item.setInsertText(attr.prefix + ":" + attr.name + "=\"" + (attr.prefix.equals("android") && attr.name.equals("id") ? "@+id/\"" : "\"")); item.setInsertText(createAttributeInsertText(attr));
item.setInsertTextFormat(InsertTextFormat.PlainText); item.setInsertTextFormat(InsertTextFormat.PlainText);
item.setSortText("1" + attr.name); item.setSortText("1" + attr.name);
item.setKind(CompletionItemKind.Snippet); item.setKind(CompletionItemKind.Snippet);
return item; return item;
} }
private CompletionItem widgetNameAsCompletion(String name, boolean slash) { private String createAttributeInsertText(Attr attr) {
final String n = simpleName(name); StringBuilder xml = new StringBuilder();
xml.append(attr.prefix);
xml.append(":");
xml.append(attr.name);
xml.append("=");
xml.append("\"");
if(attr.prefix.equals("android")
&& attr.name.equals("id")) {
xml.append("@+id/");
}
xml.append("$0");
xml.append("\"");
return xml.toString();
}
private CompletionItem widgetNameAsCompletion(Widget view, boolean slash) {
CompletionItem item = new CompletionItem(); CompletionItem item = new CompletionItem();
item.setLabel(n); item.setLabel(view.simpleName);
item.setDetail(name); item.setDetail(view.name);
item.setInsertText("<" + (slash ? "/" : "") + n); item.setInsertText(createTagInsertText(view, slash));
item.setInsertTextFormat(InsertTextFormat.PlainText); item.setInsertTextFormat(InsertTextFormat.PlainText);
item.setSortText("2" + n); item.setSortText("2" + view.simpleName);
item.setKind(CompletionItemKind.Value); item.setKind(CompletionItemKind.Value);
return item; return item;
} }
private String createTagInsertText(Widget view, boolean closing) {
StringBuilder sb = new StringBuilder();
// Don't append leading '<' and trailing '>'
// They may have been already inserted by editor upon typing '<'
sb.append(view.simpleName);
if(!closing) {
if(view.isViewGroup) {
sb.append(">$0</");
sb.append(view.simpleName);
} else {
sb.append("$0/");
}
} else {
sb.append(">$0");
}
return sb.toString();
}
private String simpleName(String name) { private String simpleName(String name) {
return name.substring(name.lastIndexOf(".") + 1); return name.substring(name.lastIndexOf(".") + 1);
@@ -142,9 +177,19 @@ public class XMLCompletionService {
if(read != null && read.length() > 0) { if(read != null && read.length() > 0) {
widgets.clear(); widgets.clear();
for(String line : read.split("\\n")) { for(String line : read.split("\\n")) {
String name = line.split("\\s")[0]; final String name = line.split("\\s")[0];
name = name.substring(1); if(name == null || name.trim().isEmpty()) continue;
widgets.add(name);
final char code = name.charAt(0);
final String viewName = name.substring(1);
final String simpleViewName = simpleName(viewName);
final boolean isViewGroup = code == 'L'; // L -> Layout, W -> Widget, P -> LayoutParam
// Don't add layout params
if(code != 'P') {
widgets.add(new Widget(viewName, simpleViewName, isViewGroup));
}
} }
Collections.sort(widgets); Collections.sort(widgets);
} }
@@ -199,9 +244,9 @@ public class XMLCompletionService {
return start <= cursorIndex && cursorIndex <= end; return start <= cursorIndex && cursorIndex <= end;
} }
public String scan(String xmlContent) { public String scan(CharSequence xmlContent) {
try { try {
XMLLexer lexer = new XMLLexer(CharStreams.fromReader(new StringReader(xmlContent))); XMLLexer lexer = new XMLLexer(CharStreams.fromReader(new StringReader(xmlContent.toString())));
Token token; Token token;
String attrName = null; String attrName = null;
while((token = lexer.nextToken()) != null && token.getType() != XMLLexer.EOF) { while((token = lexer.nextToken()) != null && token.getType() != XMLLexer.EOF) {
@@ -219,7 +264,7 @@ public class XMLCompletionService {
} }
private class Attr { class Attr {
public String prefix; public String prefix;
public String name; public String name;
@@ -240,4 +285,25 @@ public class XMLCompletionService {
return String.format("[%s=%s]", name, possibleValues.toString()); return String.format("[%s=%s]", name, possibleValues.toString());
} }
} }
class Widget implements Comparable {
String name;
String simpleName;
boolean isViewGroup;
public Widget(String name, String simpleName, boolean isViewGroup) {
this.name = name;
this.simpleName = simpleName;
this.isViewGroup = isViewGroup;
}
@Override
public int compareTo(Object p1) {
if(p1 instanceof Widget) {
final Widget that = (Widget) p1;
return this.simpleName.compareTo(that.simpleName);
}
return -1;
}
}
} }
@@ -2,8 +2,6 @@ package com.itsaky.androidide.lsp;
import com.itsaky.androidide.interfaces.EditorActivityProvider; import com.itsaky.androidide.interfaces.EditorActivityProvider;
import com.itsaky.androidide.lsp.handlers.JLSHandler; import com.itsaky.androidide.lsp.handlers.JLSHandler;
import com.itsaky.androidide.lsp.handlers.LSPHandler;
import com.itsaky.androidide.lsp.handlers.XMLLanguageServerHandler;
import com.itsaky.androidide.utils.Logger; import com.itsaky.androidide.utils.Logger;
import com.itsaky.lsp.services.IDELanguageServer; import com.itsaky.lsp.services.IDELanguageServer;
import java.io.File; import java.io.File;
@@ -40,13 +38,7 @@ public class LSP {
public static EditorActivityProvider PROVIDER; public static EditorActivityProvider PROVIDER;
private static final Logger LOG = Logger.instance("LSP"); private static final Logger LOG = Logger.instance("LSP");
public static LSPHandler Java = new JLSHandler(); public static JLSHandler Java = new JLSHandler();
public static LSPHandler XML = new XMLLanguageServerHandler();
public static void startAll(Runnable onStarted) {
LSP.Java.start(onStarted);
LSP.XML.start(onStarted);
}
/** /**
* Stores information about ports at which the specified language server must be started * Stores information about ports at which the specified language server must be started
@@ -142,7 +134,6 @@ public class LSP {
public static void shutdownAll() { public static void shutdownAll() {
// Shutdown language servers one by one // Shutdown language servers one by one
LSP.Java.shutdown(); LSP.Java.shutdown();
LSP.XML.shutdown();
} }
/** /**
@@ -1,108 +0,0 @@
package com.itsaky.androidide.lsp.client.xml;
import com.itsaky.androidide.lsp.AbstractLanguageClient;
import com.itsaky.androidide.utils.JSONUtility;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import org.eclipse.lemminx.customservice.ActionableNotification;
import org.eclipse.lemminx.customservice.XMLLanguageClientAPI;
import org.eclipse.lsp4j.ApplyWorkspaceEditParams;
import org.eclipse.lsp4j.ApplyWorkspaceEditResponse;
import org.eclipse.lsp4j.ConfigurationParams;
import org.eclipse.lsp4j.ExecuteCommandParams;
import org.eclipse.lsp4j.MessageActionItem;
import org.eclipse.lsp4j.MessageParams;
import org.eclipse.lsp4j.ProgressParams;
import org.eclipse.lsp4j.RegistrationParams;
import org.eclipse.lsp4j.SetTraceParams;
import org.eclipse.lsp4j.ShowMessageRequestParams;
import org.eclipse.lsp4j.UnregistrationParams;
import org.eclipse.lsp4j.WorkDoneProgressCreateParams;
import org.eclipse.lsp4j.WorkspaceFolder;
public class XMLLanguageClient extends AbstractLanguageClient implements XMLLanguageClientAPI {
public XMLLanguageClient(StarterListener starterListener, OnConnectedListener onConnectedListener) {
super(starterListener, onConnectedListener);
}
@Override
public void actionableNotification(ActionableNotification p1) {
LOG.info("XMLLS: actionableNotification: " + JSONUtility.prettyPrinter.toJson(p1));
}
@Override
public CompletableFuture<Object> executeClientCommand(ExecuteCommandParams p1) {
LOG.info("XMLLS: executeClientCommand: " + JSONUtility.prettyPrinter.toJson(p1));
return null;
}
@Override
public CompletableFuture<ApplyWorkspaceEditResponse> applyEdit(ApplyWorkspaceEditParams p1) {
LOG.info("XMLLS: applyEdit: " + JSONUtility.prettyPrinter.toJson(p1));
return null;
}
@Override
public CompletableFuture<List<Object>> configuration(ConfigurationParams p1) {
LOG.info("XMLLS: configuration: " + JSONUtility.prettyPrinter.toJson(p1));
return null;
}
@Override
public CompletableFuture<Void> createProgress(WorkDoneProgressCreateParams p1) {
LOG.info("XMLLS: createProgress: " + JSONUtility.prettyPrinter.toJson(p1));
return null;
}
@Override
public void notifyProgress(ProgressParams p1) {
LOG.info("XMLLS: notifyProgress: ", gson.toJson(p1));
}
@Override
public CompletableFuture<Void> refreshCodeLenses() {
LOG.info("XMLLS: refreshCodeLenses");
return null;
}
@Override
public CompletableFuture<Void> refreshSemanticTokens() {
LOG.info("XMLLS: refreshSemanticTokens: ");
return null;
}
@Override
public CompletableFuture<Void> registerCapability(RegistrationParams p1) {
LOG.info("XMLLS: registerCapability: " + JSONUtility.prettyPrinter.toJson(p1));
return null;
}
@Override
public void setTrace(SetTraceParams p1) {
LOG.info("XMLLS: setTrafe: " + JSONUtility.prettyPrinter.toJson(p1));
}
@Override
public void showMessage(MessageParams p1) {
LOG.info("XMLLS: showMessage: " + JSONUtility.prettyPrinter.toJson(p1));
}
@Override
public CompletableFuture<MessageActionItem> showMessageRequest(ShowMessageRequestParams p1) {
LOG.info("XMLLS: showMessageRequest: " + JSONUtility.prettyPrinter.toJson(p1));
return null;
}
@Override
public CompletableFuture<Void> unregisterCapability(UnregistrationParams p1) {
LOG.info("XMLLS: unregisterCapability: " + JSONUtility.prettyPrinter.toJson(p1));
return null;
}
@Override
public CompletableFuture<List<WorkspaceFolder>> workspaceFolders() {
LOG.info("XMLLS: workspaceFolders");
return null;
}
}
@@ -1,4 +1,5 @@
package com.itsaky.androidide.lsp.handlers; package com.itsaky.androidide.lsp.handlers;
import java.util.Optional; import java.util.Optional;
import org.eclipse.lsp4j.InitializeResult; import org.eclipse.lsp4j.InitializeResult;
@@ -1,82 +0,0 @@
package com.itsaky.androidide.lsp.handlers;
import com.itsaky.androidide.lsp.LSP;
import com.itsaky.androidide.lsp.LSPProvider;
import com.itsaky.androidide.lsp.LanguageServerWrapper;
import com.itsaky.androidide.lsp.client.xml.XMLLanguageClient;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import org.eclipse.lemminx.XMLLanguageServer;
import org.eclipse.lsp4j.InitializeParams;
import org.eclipse.lsp4j.InitializeResult;
import org.eclipse.lsp4j.InitializedParams;
import org.eclipse.lsp4j.WorkspaceFolder;
public class XMLLanguageServerHandler implements LSPHandler {
private LanguageServerWrapper server;
private XMLLanguageClient client;
@Override
public void start(Runnable onStarted) {
server = new LanguageServerWrapper(new XMLLanguageServer());
client = new XMLLanguageClient(null, null);
((XMLLanguageServer) server.getServer()).setClient(client);
LSPProvider.setLanguageServer(LSPProvider.LANGUAGE_XML, server);
LSPProvider.setClientForLanguage(LSPProvider.LANGUAGE_XML, client);
if(onStarted != null)
onStarted.run();
}
@Override
public Optional<InitializeResult> init(String rootPath) {
if(this.server == null) {
return Optional.empty();
}
final List<WorkspaceFolder> folders = new ArrayList<>();
final File file = new File(rootPath);
final WorkspaceFolder folder = new WorkspaceFolder();
folder.setUri(file.toURI().toString());
folder.setName(file.getName().toString());
folders.add(folder);
InitializeParams params = new InitializeParams();
params.setClientInfo(LSP.getClientInfo());
params.setWorkspaceFolders(folders);
params.setCapabilities(LSP.getCapabilities());
CompletableFuture<InitializeResult> initResult = server.initialize(params);
try {
InitializeResult result = initResult.get();
if(result.getCapabilities() != null) {
LSPProvider.setServerCapabilitesForLanguage(LSPProvider.LANGUAGE_XML, result.getCapabilities());
}
return Optional.of(result);
} catch (Throwable e) {
return Optional.empty();
}
}
@Override
public void initialized() {
if(server == null)
return;
server.initialized(new InitializedParams());
}
@Override
public void shutdown() {
server.shutdown();
}
}
@@ -37,7 +37,7 @@ public interface AutoCompleteProvider {
* @param line The line of cursor * @param line The line of cursor
* @return Analyzed items * @return Analyzed items
*/ */
List<CompletionItem> getAutoCompleteItems(String fileUri, String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int index, int line, int column) throws Exception; List<CompletionItem> getAutoCompleteItems(CharSequence content, String fileUri, String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int index, int line, int column) throws Exception;
} }
@@ -89,7 +89,7 @@ public class EmptyLanguage extends AbstractEditorLanguage {
public static class EmptyAutoCompleteProvider implements AutoCompleteProvider { public static class EmptyAutoCompleteProvider implements AutoCompleteProvider {
@Override @Override
public List<CompletionItem> getAutoCompleteItems(String fileUri, String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int index, int line, int column) { public List<CompletionItem> getAutoCompleteItems(CharSequence content, String fileUri, String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int index, int line, int column) {
return new ArrayList<CompletionItem>(); return new ArrayList<CompletionItem>();
} }
@@ -39,6 +39,7 @@ import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.TextEdit; import org.eclipse.lsp4j.TextEdit;
import org.eclipse.lsp4j.Command; import org.eclipse.lsp4j.Command;
import io.github.rosemoe.editor.text.ContentLine;
public class EditorAutoCompleteWindow extends EditorBasePopupWindow { public class EditorAutoCompleteWindow extends EditorBasePopupWindow {
private final static String TIP = "Refreshing..."; private final static String TIP = "Refreshing...";
@@ -187,12 +188,11 @@ public class EditorAutoCompleteWindow extends EditorBasePopupWindow {
* @param pos Index of auto complete item * @param pos Index of auto complete item
*/ */
public void select(int pos) { public void select(int pos) {
CompletionItem item = ((EditorCompletionAdapter) mBinding.list.getAdapter()).getItem(pos); final CompletionItem item = ((EditorCompletionAdapter) mBinding.list.getAdapter()).getItem(pos);
Range range = getIdentifierRange(mEditor.getCursor().getRight(), mEditor.getText()); final Range range = getIdentifierRange(mEditor.getCursor().getRight(), mEditor.getText());
String text = item.getInsertText() == null ? item.getLabel() : item.getInsertText(); String text = item.getInsertText() == null ? item.getLabel() : item.getInsertText();
final boolean shiftLeft = text.contains("$0"); final boolean shiftCursor = item.getInsertText() != null && text.contains("$0");
final boolean atEnd = text.endsWith("$0");
text = text.replace("$0", "");
mEditor.getText().delete( mEditor.getText().delete(
range.getStart().getLine(), range.getStart().getLine(),
@@ -202,8 +202,14 @@ public class EditorAutoCompleteWindow extends EditorBasePopupWindow {
); );
mEditor.getCursor().onCommitText(text); mEditor.getCursor().onCommitText(text);
if(shiftLeft && !atEnd) { if(shiftCursor) {
mEditor.moveSelectionLeft(); final int line = mEditor.getCursor().getLeftLine();
final String lineText = mEditor.getText().getLineString(line);
final int column = lineText.lastIndexOf("$0");
if(column != -1){
mEditor.setSelection(line, column);
mEditor.getText().delete(line, column, line, column + 2);
}
} }
final List<TextEdit> edits = item.getAdditionalTextEdits(); final List<TextEdit> edits = item.getAdditionalTextEdits();
@@ -226,7 +232,7 @@ public class EditorAutoCompleteWindow extends EditorBasePopupWindow {
Command cmd = item.getCommand(); Command cmd = item.getCommand();
if("editor.action.triggerParameterHints".equals(cmd.getCommand())) { if("editor.action.triggerParameterHints".equals(cmd.getCommand())) {
// Trigger signature help request included in CompletionItem // Trigger signature help request if included in CompletionItem
// TODO Don't rely on CompletionItem for requesting signature help // TODO Don't rely on CompletionItem for requesting signature help
// If the insert text contains '(', automatically trigger this action // If the insert text contains '(', automatically trigger this action
mEditor.signatureHelp("("); mEditor.signatureHelp("(");
@@ -308,6 +314,7 @@ public class EditorAutoCompleteWindow extends EditorBasePopupWindow {
private final long mTime; private final long mTime;
private final String mPrefix; private final String mPrefix;
private final String mFileUri; private final String mFileUri;
private final CharSequence mContent;
private final boolean mInner; private final boolean mInner;
private final TextAnalyzeResult mColors; private final TextAnalyzeResult mColors;
private final int mIndex; private final int mIndex;
@@ -319,6 +326,7 @@ public class EditorAutoCompleteWindow extends EditorBasePopupWindow {
mTime = requestTime; mTime = requestTime;
mPrefix = prefix; mPrefix = prefix;
mFileUri = mEditor.getFile() != null ? mEditor.getFile().toURI().toString() : null; mFileUri = mEditor.getFile() != null ? mEditor.getFile().toURI().toString() : null;
mContent = mEditor.getText();
mColors = mEditor.getTextAnalyzeResult(); mColors = mEditor.getTextAnalyzeResult();
mIndex = mEditor.getCursor().getLeft(); mIndex = mEditor.getCursor().getLeft();
mLine = mEditor.getCursor().getLeftLine(); mLine = mEditor.getCursor().getLeftLine();
@@ -329,7 +337,7 @@ public class EditorAutoCompleteWindow extends EditorBasePopupWindow {
@Override @Override
public void run() { public void run() {
try { try {
displayResults(mLocalProvider.getAutoCompleteItems(mFileUri, mPrefix, mInner, mColors, mIndex, mLine, mColumn), mTime); displayResults(mLocalProvider.getAutoCompleteItems(mContent, mFileUri, mPrefix, mInner, mColors, mIndex, mLine, mColumn), mTime);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
displayResults(new ArrayList<>(), mTime); displayResults(new ArrayList<>(), mTime);
+2
View File
@@ -13,6 +13,8 @@ buildscript {
javaSourceVersion = JavaVersion.VERSION_1_8 javaSourceVersion = JavaVersion.VERSION_1_8
javaTargetVersion = JavaVersion.VERSION_1_8 javaTargetVersion = JavaVersion.VERSION_1_8
stringfogEnabled = false
} }
repositories { repositories {
+1 -1
View File
@@ -6,7 +6,7 @@
# http://www.gradle.org/docs/current/userguide/build_environment.html # http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process. # Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings. # The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx700m org.gradle.jvmargs=-Xmx512m
# When configured, Gradle will run in incubating parallel mode. # When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit # This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
-1
View File
@@ -1 +0,0 @@
<manifest package="com.itsaky.lsp.lemminx"/>
@@ -1,66 +0,0 @@
/*******************************************************************************
* Copyright (c) 2020 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
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx;
import java.io.IOException;
import java.io.InputStream;
/**
* No-Op {@link InputStream}
*
* @author Fred Bricon
*/
class NoOpInputStream extends InputStream {
@Override
public int read() throws IOException {
return -1;
}
@Override
public int read(byte[] b) throws IOException {
return -1;
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
return -1;
}
@Override
public int available() throws IOException {
return 0;
}
@Override
public synchronized void mark(int readlimit) {
}
@Override
public boolean markSupported() {
return true;
}
@Override
public synchronized void reset() throws IOException {
}
@Override
public long skip(long n) throws IOException {
return 0;
}
@Override
public void close() throws IOException {
}
}
@@ -1,36 +0,0 @@
/*******************************************************************************
* Copyright (c) 2020 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
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx;
import java.io.IOException;
import java.io.OutputStream;
/**
* No-Op {@link OutputStream}.
*
* @author Fred Bricon
*/
class NoOpOutputStream extends OutputStream {
@Override
public void write(int b) throws IOException {
}
@Override
public void write(byte[] b, int off, int len) {
}
@Override
public void write(byte[] b) throws IOException {
}
}
@@ -1,27 +0,0 @@
/*******************************************************************************
* Copyright (c) 2020 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
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx;
import java.io.PrintStream;
/**
* No-Op {@link PrintStream}.
*
* @author Fred Bricon
*/
class NoOpPrintStream extends PrintStream {
public NoOpPrintStream() {
super(new NoOpOutputStream());
}
}
@@ -1,349 +0,0 @@
/**
* Copyright (c) 2018 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
* http://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
* Red Hat Inc. - Dynamic Server capabilities
*/
package org.eclipse.lemminx;
import static org.eclipse.lsp4j.jsonrpc.CompletableFutures.computeAsync;
import java.util.Arrays;
import java.util.Collection;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import org.eclipse.lemminx.client.ExtendedClientCapabilities;
import org.eclipse.lemminx.commons.ModelTextDocument;
import org.eclipse.lemminx.commons.ParentProcessWatcher.ProcessLanguageServer;
import org.eclipse.lemminx.customservice.ActionableNotification;
import org.eclipse.lemminx.customservice.AutoCloseTagResponse;
import org.eclipse.lemminx.customservice.XMLLanguageClientAPI;
import org.eclipse.lemminx.customservice.XMLLanguageServerAPI;
import org.eclipse.lemminx.dom.DOMDocument;
import org.eclipse.lemminx.extensions.contentmodel.settings.ContentModelSettings;
import org.eclipse.lemminx.extensions.contentmodel.settings.XMLValidationSettings;
import org.eclipse.lemminx.logs.LogHelper;
import org.eclipse.lemminx.services.IXMLDocumentProvider;
import org.eclipse.lemminx.services.IXMLNotificationService;
import org.eclipse.lemminx.services.IXMLValidationService;
import org.eclipse.lemminx.services.XMLLanguageService;
import org.eclipse.lemminx.settings.AllXMLSettings;
import org.eclipse.lemminx.settings.InitializationOptionsSettings;
import org.eclipse.lemminx.settings.ServerSettings;
import org.eclipse.lemminx.settings.SharedSettings;
import org.eclipse.lemminx.settings.XMLCodeLensSettings;
import org.eclipse.lemminx.settings.XMLCompletionSettings;
import org.eclipse.lemminx.settings.XMLFormattingOptions;
import org.eclipse.lemminx.settings.XMLGeneralClientSettings;
import org.eclipse.lemminx.settings.XMLPreferences;
import org.eclipse.lemminx.settings.XMLSymbolSettings;
import org.eclipse.lemminx.settings.XMLTelemetrySettings;
import org.eclipse.lemminx.settings.capabilities.InitializationOptionsExtendedClientCapabilities;
import org.eclipse.lemminx.settings.capabilities.ServerCapabilitiesInitializer;
import org.eclipse.lemminx.settings.capabilities.XMLCapabilityManager;
import org.eclipse.lemminx.telemetry.TelemetryManager;
import org.eclipse.lemminx.utils.FilesUtils;
import org.eclipse.lemminx.utils.platform.Platform;
import org.eclipse.lsp4j.Command;
import org.eclipse.lsp4j.InitializeParams;
import org.eclipse.lsp4j.InitializeResult;
import org.eclipse.lsp4j.InitializedParams;
import org.eclipse.lsp4j.MessageParams;
import org.eclipse.lsp4j.MessageType;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.ServerCapabilities;
import org.eclipse.lsp4j.TextDocumentPositionParams;
import org.eclipse.lsp4j.services.LanguageClient;
import org.eclipse.lsp4j.services.TextDocumentService;
import org.eclipse.lsp4j.services.WorkspaceService;
/**
* XML language server.
*
*/
public class XMLLanguageServer
implements ProcessLanguageServer, XMLLanguageServerAPI, IXMLDocumentProvider,
IXMLNotificationService, IXMLValidationService {
private static final Logger LOGGER = Logger.getLogger(XMLLanguageServer.class.getName());
private final XMLLanguageService xmlLanguageService;
private final XMLTextDocumentService xmlTextDocumentService;
private final XMLWorkspaceService xmlWorkspaceService;
private XMLLanguageClientAPI languageClient;
private final ScheduledExecutorService delayer;
private Integer parentProcessId;
private XMLCapabilityManager capabilityManager;
private TelemetryManager telemetryManager;
public XMLLanguageServer() {
xmlTextDocumentService = new XMLTextDocumentService(this);
xmlWorkspaceService = new XMLWorkspaceService(this);
xmlLanguageService = new XMLLanguageService();
xmlLanguageService.setDocumentProvider(this);
xmlLanguageService.setNotificationService(this);
xmlLanguageService.setCommandService(xmlWorkspaceService);
xmlLanguageService.setValidationService(this);
delayer = Executors.newScheduledThreadPool(1);
}
@Override
public CompletableFuture<InitializeResult> initialize(InitializeParams params) {
Object initOptions = InitializationOptionsSettings.getSettings(params);
Object xmlSettings = AllXMLSettings.getAllXMLSettings(initOptions);
XMLGeneralClientSettings settings = XMLGeneralClientSettings.getGeneralXMLSettings(xmlSettings);
LogHelper.initializeRootLogger(languageClient, settings == null? null : settings.getLogs());
LOGGER.info("Initializing XML Language server" + System.lineSeparator() + Platform.details());
this.parentProcessId = params.getProcessId();
xmlLanguageService.setTelemetryManager(getTelemetryManager());
// Update XML language service extensions with InitializeParams
xmlLanguageService.initializeParams(params);
ExtendedClientCapabilities extendedClientCapabilities = InitializationOptionsExtendedClientCapabilities
.getExtendedClientCapabilities(params);
capabilityManager.setClientCapabilities(params.getCapabilities(), extendedClientCapabilities);
xmlTextDocumentService.updateClientCapabilities(capabilityManager.getClientCapabilities().capabilities,
capabilityManager.getClientCapabilities().getExtendedCapabilities());
updateSettings(initOptions, false /* already configured logging*/ );
ServerCapabilities nonDynamicServerCapabilities = ServerCapabilitiesInitializer.getNonDynamicServerCapabilities(
capabilityManager.getClientCapabilities(), xmlTextDocumentService.isIncrementalSupport());
return CompletableFuture.completedFuture(new InitializeResult(nonDynamicServerCapabilities));
}
/*
* Registers all capabilities that do not support client side preferences to
* turn on/off
*
* (non-Javadoc)
*
* @see org.eclipse.lsp4j.services.LanguageServer#initialized(org.eclipse.lsp4j.
* InitializedParams)
*/
@Override
public void initialized(InitializedParams params) {
capabilityManager.initializeCapabilities();
getTelemetryManager().onInitialized(params);
}
/**
* Update XML settings configured from the client.
*
* @param initOptions the XML settings
*/
public synchronized void updateSettings(Object initOptions) {
updateSettings(initOptions, true);
}
/**
* Update XML settings configured from the client.
*
* @param initOptions Settings the XML settings
* @param initLogs whether to initialize the log handlers
*/
private synchronized void updateSettings(Object initOptions, boolean initLogs) {
if (initOptions == null) {
return;
}
// Update client settings
Object initSettings = AllXMLSettings.getAllXMLSettings(initOptions);
XMLGeneralClientSettings xmlClientSettings = XMLGeneralClientSettings.getGeneralXMLSettings(initSettings);
if (xmlClientSettings != null) {
if (initLogs) {
// Update logs settings
LogHelper.initializeRootLogger(languageClient, xmlClientSettings.getLogs());
}
XMLTelemetrySettings newTelemetry = xmlClientSettings.getTelemetry();
if (newTelemetry != null) {
getTelemetryManager().setEnabled(newTelemetry.isEnabled());
}
// Update format settings
XMLFormattingOptions formatterSettings = xmlClientSettings.getFormat();
if (formatterSettings != null) {
xmlTextDocumentService.getSharedFormattingSettings().merge(formatterSettings);
}
XMLCompletionSettings newCompletions = xmlClientSettings.getCompletion();
if (newCompletions != null) {
xmlTextDocumentService.updateCompletionSettings(newCompletions);
}
XMLSymbolSettings newSymbols = xmlClientSettings.getSymbols();
if (newSymbols != null) {
xmlTextDocumentService.updateSymbolSettings(newSymbols);
}
XMLCodeLensSettings newCodeLens = xmlClientSettings.getCodeLens();
if (newCodeLens != null) {
xmlTextDocumentService.updateCodeLensSettings(newCodeLens);
}
XMLPreferences newPreferences = xmlClientSettings.getPreferences();
if (newPreferences != null) {
xmlTextDocumentService.updatePreferences(newPreferences);
}
ServerSettings serverSettings = xmlClientSettings.getServer();
if (serverSettings != null) {
String workDir = serverSettings.getNormalizedWorkDir();
FilesUtils.setCachePathSetting(workDir);
}
}
ContentModelSettings cmSettings = ContentModelSettings.getContentModelXMLSettings(initSettings);
if (cmSettings != null) {
XMLValidationSettings validationSettings = cmSettings.getValidation();
xmlTextDocumentService.getValidationSettings().merge(validationSettings);
}
// Update XML language service extensions
xmlTextDocumentService.updateSettings(initSettings);
}
@Override
public CompletableFuture<Object> shutdown() {
xmlLanguageService.dispose();
if (capabilityManager.getClientCapabilities().getExtendedCapabilities().shouldLanguageServerExitOnShutdown()) {
delayer.schedule(() -> exit(0) , 1, TimeUnit.SECONDS);
}
return computeAsync(cc -> new Object());
}
@Override
public void exit() {
exit(0);
}
@Override
public void exit(int exitCode) {
delayer.shutdown();
System.exit(exitCode);
}
@Override
public TextDocumentService getTextDocumentService() {
return xmlTextDocumentService;
}
@Override
public WorkspaceService getWorkspaceService() {
return xmlWorkspaceService;
}
public void setClient(LanguageClient languageClient) {
this.languageClient = (XMLLanguageClientAPI) languageClient;
capabilityManager = new XMLCapabilityManager(this.languageClient, xmlTextDocumentService);
telemetryManager = new TelemetryManager(languageClient);
}
public XMLLanguageClientAPI getLanguageClient() {
return languageClient;
}
public XMLLanguageService getXMLLanguageService() {
return xmlLanguageService;
}
public SharedSettings getSettings() {
return xmlTextDocumentService.getSharedSettings();
}
public ScheduledFuture<?> schedule(Runnable command, int delay, TimeUnit unit) {
return delayer.schedule(command, delay, unit);
}
@Override
public long getParentProcessId() {
return parentProcessId != null ? parentProcessId : 0;
}
@Override
public CompletableFuture<AutoCloseTagResponse> closeTag(TextDocumentPositionParams params) {
return xmlTextDocumentService.computeDOMAsync(params.getTextDocument(), (cancelChecker, xmlDocument) -> {
return getXMLLanguageService().doAutoClose(xmlDocument, params.getPosition(), getSettings().getCompletionSettings(), cancelChecker);
});
}
@Override
public CompletableFuture<Position> matchingTagPosition(TextDocumentPositionParams params) {
return xmlTextDocumentService.computeDOMAsync(params.getTextDocument(), (cancelChecker, xmlDocument) -> {
return getXMLLanguageService().getMatchingTagPosition(xmlDocument, params.getPosition(), cancelChecker);
});
}
@Override
public DOMDocument getDocument(String uri) {
ModelTextDocument<DOMDocument> document = xmlTextDocumentService.getDocument(uri);
return document != null ? document.getModel().getNow(null) : null;
}
@Override
public void sendNotification(String message, MessageType messageType, Command... commands) {
SharedSettings sharedSettings = getSharedSettings();
if (sharedSettings.isActionableNotificationSupport() && sharedSettings.isOpenSettingsCommandSupport()) {
ActionableNotification notification = new ActionableNotification().withSeverity(messageType)
.withMessage(message).withCommands(Arrays.asList(commands));
languageClient.actionableNotification(notification);
} else {
// the open settings command is not supported by the client, display a simple
// message with LSP
languageClient.showMessage(new MessageParams(messageType, message));
}
}
@Override
public SharedSettings getSharedSettings() {
return xmlTextDocumentService.getSharedSettings();
}
@Override
public Collection<DOMDocument> getAllDocuments() {
return xmlTextDocumentService.allDocuments().stream()
.map(m -> m.getModel().getNow(null))
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
@Override
public void validate(DOMDocument document) {
xmlTextDocumentService.validate(document);
}
public XMLCapabilityManager getCapabilityManager() {
return capabilityManager;
}
/**
* Returns the telemetry manager.
*
* @return the telemetry manager.
*/
public TelemetryManager getTelemetryManager() {
return telemetryManager;
}
}
@@ -1,127 +0,0 @@
/**
* Copyright (c) 2018 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
* http://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
*/
package org.eclipse.lemminx;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Authenticator;
import java.net.PasswordAuthentication;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.function.Function;
import org.eclipse.lemminx.commons.ParentProcessWatcher;
import org.eclipse.lemminx.customservice.XMLLanguageClientAPI;
import org.eclipse.lsp4j.jsonrpc.Launcher;
import org.eclipse.lsp4j.jsonrpc.MessageConsumer;
import org.eclipse.lsp4j.launch.LSPLauncher.Builder;
import org.eclipse.lsp4j.services.LanguageClient;
import org.eclipse.lsp4j.services.LanguageServer;
public class XMLServerLauncher {
/**
* Calls {@link #launch(InputStream, OutputStream)}, using the standard input
* and output streams.
*/
public static void main(String[] args) {
final String HTTP_PROXY_HOST = System.getenv("HTTP_PROXY_HOST");
final String HTTP_PROXY_PORT = System.getenv("HTTP_PROXY_PORT");
final String HTTP_PROXY_USERNAME = System.getenv("HTTP_PROXY_USERNAME");
final String HTTP_PROXY_PASSWORD = System.getenv("HTTP_PROXY_PASSWORD");
final boolean LEMMINX_DEBUG = System.getenv("LEMMINX_DEBUG") != null;
if (HTTP_PROXY_HOST != null && HTTP_PROXY_PORT != null) {
System.setProperty("http.proxyHost", HTTP_PROXY_HOST);
System.setProperty("http.proxyPort", HTTP_PROXY_PORT);
}
if (HTTP_PROXY_USERNAME != null && HTTP_PROXY_PASSWORD != null) {
System.setProperty("http.proxyUser", HTTP_PROXY_USERNAME);
System.setProperty("http.proxyPassword", HTTP_PROXY_PASSWORD);
}
final String username = System.getProperty("http.proxyUser");
final String password = System.getProperty("http.proxyPassword");
if (username != null && password != null) {
Authenticator.setDefault(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password.toCharArray());
}
});
}
InputStream in = System.in;
PrintStream out = System.out;
System.setIn(new NoOpInputStream());
System.setOut(new NoOpPrintStream());
if (!LEMMINX_DEBUG) {
System.setErr(new NoOpPrintStream());
}
launch(in, out);
}
/**
* Launches {@link XMLLanguageServer} and makes it accessible through the JSON
* RPC protocol defined by the LSP.
*
* @param launcherFuture The future returned by
* {@link org.eclipse.lsp4j.jsonrpc.Launcher#startListening()}.
* (I'm not 100% sure how it meant to be used though, as
* it's undocumented...)
*/
public static Future<?> launch(InputStream in, OutputStream out) {
XMLLanguageServer server = new XMLLanguageServer();
Function<MessageConsumer, MessageConsumer> wrapper;
if ("false".equals(System.getProperty("watchParentProcess"))) {
wrapper = it -> it;
} else {
wrapper = new ParentProcessWatcher(server);
}
Launcher<LanguageClient> launcher = createServerLauncher(server, in, out, Executors.newCachedThreadPool(), wrapper);
server.setClient(launcher.getRemoteProxy());
return launcher.startListening();
}
/**
* Create a new Launcher for a language server and an input and output stream.
* Threads are started with the given executor service. The wrapper function is
* applied to the incoming and outgoing message streams so additional message
* handling such as validation and tracing can be included.
*
* @param server - the server that receives method calls from the
* remote client
* @param in - input stream to listen for incoming messages
* @param out - output stream to send outgoing messages
* @param executorService - the executor service used to start threads
* @param wrapper - a function for plugging in additional message
* consumers
*/
private static Launcher<LanguageClient> createServerLauncher(LanguageServer server, InputStream in, OutputStream out,
ExecutorService executorService, Function<MessageConsumer, MessageConsumer> wrapper) {
return new Builder<LanguageClient>().
setLocalService(server)
.setRemoteInterface(XMLLanguageClientAPI.class) // Set client as XML language client
.setInput(in)
.setOutput(out)
.setExecutorService(executorService)
.wrapMessages(wrapper)
.create();
}
}
@@ -1,75 +0,0 @@
/**
* Copyright (c) 2018 TypeFox GmbH (http://www.typefox.io) 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
*
* @author Dennis Huebner <dennis.huebner@gmail.com> - Initial contribution and API
*/
package org.eclipse.lemminx;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.Channels;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.eclipse.lsp4j.jsonrpc.Launcher;
import org.eclipse.lsp4j.jsonrpc.MessageConsumer;
import org.eclipse.lsp4j.services.LanguageClient;
public class XMLServerSocketLauncher {
private static final int DEFAULT_PORT = 5_008;
/**
* Calls {@link #launch(String[])}
*/
public static void main(String[] args) throws Exception {
new XMLServerSocketLauncher().launch(args);
}
/**
* Launches {@link XMLLanguageServer} using asynchronous server-socket channel and makes it accessible through the JSON
* RPC protocol defined by the LSP.
*
* @param args standard launch arguments. may contain <code>--port</code> argument to change the default port 5008
*/
public void launch(String[] args) throws Exception {
AsynchronousServerSocketChannel _open = AsynchronousServerSocketChannel.open();
int _port = getPort(args);
InetSocketAddress _inetSocketAddress = new InetSocketAddress("0.0.0.0", _port);
final AsynchronousServerSocketChannel serverSocket = _open.bind(_inetSocketAddress);
while (true) {
final AsynchronousSocketChannel socketChannel = serverSocket.accept().get();
final InputStream in = Channels.newInputStream(socketChannel);
final OutputStream out = Channels.newOutputStream(socketChannel);
final ExecutorService executorService = Executors.newCachedThreadPool();
XMLLanguageServer languageServer = new XMLLanguageServer();
final Launcher<LanguageClient> launcher = Launcher.createIoLauncher(languageServer, LanguageClient.class,
in, out, executorService, (MessageConsumer it) -> {
return it;
});
languageServer.setClient(launcher.getRemoteProxy());
launcher.startListening();
}
}
protected int getPort(final String... args) {
for (int i = 0; (i < (args.length - 1)); i++) {
String _get = args[i];
boolean _equals = Objects.equals(_get, "--port");
if (_equals) {
return Integer.parseInt(args[(i + 1)]);
}
}
return DEFAULT_PORT;
}
}
@@ -1,733 +0,0 @@
/**
* Copyright (c) 2018 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
* http://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
*/
package org.eclipse.lemminx;
import static org.eclipse.lsp4j.jsonrpc.CompletableFutures.computeAsync;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.BiFunction;
import java.util.function.Predicate;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import org.eclipse.lemminx.client.ExtendedClientCapabilities;
import org.eclipse.lemminx.client.LimitExceededWarner;
import org.eclipse.lemminx.client.LimitFeature;
import org.eclipse.lemminx.commons.ModelTextDocument;
import org.eclipse.lemminx.commons.ModelTextDocuments;
import org.eclipse.lemminx.commons.TextDocument;
import org.eclipse.lemminx.commons.TextDocuments;
import org.eclipse.lemminx.dom.DOMDocument;
import org.eclipse.lemminx.dom.DOMParser;
import org.eclipse.lemminx.extensions.contentmodel.settings.XMLValidationSettings;
import org.eclipse.lemminx.services.DocumentSymbolsResult;
import org.eclipse.lemminx.services.SymbolInformationResult;
import org.eclipse.lemminx.services.XMLLanguageService;
import org.eclipse.lemminx.services.extensions.save.AbstractSaveContext;
import org.eclipse.lemminx.settings.CompositeSettings;
import org.eclipse.lemminx.settings.SharedSettings;
import org.eclipse.lemminx.settings.XMLCodeLensSettings;
import org.eclipse.lemminx.settings.XMLCompletionSettings;
import org.eclipse.lemminx.settings.XMLFormattingOptions;
import org.eclipse.lemminx.settings.XMLPreferences;
import org.eclipse.lemminx.settings.XMLSymbolSettings;
import org.eclipse.lemminx.utils.XMLPositionUtility;
import org.eclipse.lsp4j.ClientCapabilities;
import org.eclipse.lsp4j.CodeAction;
import org.eclipse.lsp4j.CodeActionParams;
import org.eclipse.lsp4j.CodeLens;
import org.eclipse.lsp4j.CodeLensParams;
import org.eclipse.lsp4j.Command;
import org.eclipse.lsp4j.CompletionItem;
import org.eclipse.lsp4j.CompletionList;
import org.eclipse.lsp4j.CompletionParams;
import org.eclipse.lsp4j.ConfigurationItem;
import org.eclipse.lsp4j.ConfigurationParams;
import org.eclipse.lsp4j.DefinitionParams;
import org.eclipse.lsp4j.DidChangeTextDocumentParams;
import org.eclipse.lsp4j.DidCloseTextDocumentParams;
import org.eclipse.lsp4j.DidOpenTextDocumentParams;
import org.eclipse.lsp4j.DidSaveTextDocumentParams;
import org.eclipse.lsp4j.DocumentFormattingParams;
import org.eclipse.lsp4j.DocumentHighlight;
import org.eclipse.lsp4j.DocumentHighlightParams;
import org.eclipse.lsp4j.DocumentLink;
import org.eclipse.lsp4j.DocumentLinkParams;
import org.eclipse.lsp4j.DocumentRangeFormattingParams;
import org.eclipse.lsp4j.DocumentSymbol;
import org.eclipse.lsp4j.DocumentSymbolParams;
import org.eclipse.lsp4j.FoldingRange;
import org.eclipse.lsp4j.FoldingRangeRequestParams;
import org.eclipse.lsp4j.Hover;
import org.eclipse.lsp4j.HoverParams;
import org.eclipse.lsp4j.LinkedEditingRangeParams;
import org.eclipse.lsp4j.LinkedEditingRanges;
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.LocationLink;
import org.eclipse.lsp4j.PublishDiagnosticsParams;
import org.eclipse.lsp4j.ReferenceParams;
import org.eclipse.lsp4j.RenameParams;
import org.eclipse.lsp4j.SelectionRange;
import org.eclipse.lsp4j.SelectionRangeParams;
import org.eclipse.lsp4j.SymbolInformation;
import org.eclipse.lsp4j.TextDocumentClientCapabilities;
import org.eclipse.lsp4j.TextDocumentContentChangeEvent;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.eclipse.lsp4j.TextEdit;
import org.eclipse.lsp4j.TypeDefinitionParams;
import org.eclipse.lsp4j.WorkspaceEdit;
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.eclipse.lsp4j.jsonrpc.validation.NonNull;
import org.eclipse.lsp4j.services.TextDocumentService;
import com.google.gson.JsonPrimitive;
/**
* XML text document service.
*
*/
public class XMLTextDocumentService implements TextDocumentService {
private static final Logger LOGGER = Logger.getLogger(XMLTextDocumentService.class.getName());
private final XMLLanguageServer xmlLanguageServer;
private final TextDocuments<ModelTextDocument<DOMDocument>> documents;
private SharedSettings sharedSettings;
private LimitExceededWarner limitExceededWarner;
/**
* Enumeration for Validation triggered by.
*
*/
private static enum TriggeredBy {
didOpen, //
didChange, //
Other;
}
/**
* Save context.
*/
class SaveContext extends AbstractSaveContext {
private final Collection<ModelTextDocument<DOMDocument>> documentsToValidate;
public SaveContext(Object settings) {
super(settings);
this.documentsToValidate = new ArrayList<>();
}
public SaveContext(String uri) {
super(uri);
this.documentsToValidate = new ArrayList<>();
}
@Override
public void collectDocumentToValidate(Predicate<DOMDocument> validateDocumentPredicate) {
documents.all().stream().forEach(document -> {
DOMDocument xmlDocument = document.getModel().getNow(null);
if (xmlDocument != null && !documentsToValidate.contains(document)
&& validateDocumentPredicate.test(xmlDocument)) {
documentsToValidate.add(document);
}
});
}
@Override
public DOMDocument getDocument(String uri) {
return xmlLanguageServer.getDocument(uri);
}
public void triggerValidationIfNeeded() {
triggerValidationFor(documentsToValidate);
}
}
final ScheduledExecutorService delayer = Executors.newScheduledThreadPool(2);
private boolean codeActionLiteralSupport;
private boolean hierarchicalDocumentSymbolSupport;
private boolean definitionLinkSupport;
private boolean typeDefinitionLinkSupport;
public XMLTextDocumentService(XMLLanguageServer xmlLanguageServer) {
this.xmlLanguageServer = xmlLanguageServer;
DOMParser parser = DOMParser.getInstance();
this.documents = new ModelTextDocuments<DOMDocument>((document, cancelChecker) -> {
return parser.parse(document, getXMLLanguageService().getResolverExtensionManager(), true, cancelChecker);
});
this.sharedSettings = new SharedSettings();
this.limitExceededWarner = null;
}
public void updateClientCapabilities(ClientCapabilities capabilities,
ExtendedClientCapabilities extendedClientCapabilities) {
if (capabilities != null) {
TextDocumentClientCapabilities textDocumentClientCapabilities = capabilities.getTextDocument();
if (textDocumentClientCapabilities != null) {
sharedSettings.getCompletionSettings().setCapabilities(textDocumentClientCapabilities.getCompletion());
sharedSettings.getFoldingSettings().setCapabilities(textDocumentClientCapabilities.getFoldingRange());
sharedSettings.getHoverSettings().setCapabilities(textDocumentClientCapabilities.getHover());
sharedSettings.getValidationSettings()
.setCapabilities(textDocumentClientCapabilities.getPublishDiagnostics());
codeActionLiteralSupport = textDocumentClientCapabilities.getCodeAction() != null
&& textDocumentClientCapabilities.getCodeAction().getCodeActionLiteralSupport() != null;
hierarchicalDocumentSymbolSupport = textDocumentClientCapabilities.getDocumentSymbol() != null
&& textDocumentClientCapabilities.getDocumentSymbol()
.getHierarchicalDocumentSymbolSupport() != null
&& textDocumentClientCapabilities.getDocumentSymbol().getHierarchicalDocumentSymbolSupport();
definitionLinkSupport = textDocumentClientCapabilities.getDefinition() != null
&& textDocumentClientCapabilities.getDefinition().getLinkSupport() != null
&& textDocumentClientCapabilities.getDefinition().getLinkSupport();
typeDefinitionLinkSupport = textDocumentClientCapabilities.getTypeDefinition() != null
&& textDocumentClientCapabilities.getTypeDefinition().getLinkSupport() != null
&& textDocumentClientCapabilities.getTypeDefinition().getLinkSupport();
}
// Workspace settings
sharedSettings.getWorkspaceSettings().setCapabilities(capabilities.getWorkspace());
}
if (extendedClientCapabilities != null) {
// Extended client capabilities
sharedSettings.getCodeLensSettings().setCodeLens(extendedClientCapabilities.getCodeLens());
sharedSettings
.setActionableNotificationSupport(extendedClientCapabilities.isActionableNotificationSupport());
sharedSettings.setOpenSettingsCommandSupport(extendedClientCapabilities.isOpenSettingsCommandSupport());
sharedSettings.setBindingWizardSupport(extendedClientCapabilities.isBindingWizardSupport());
}
}
@Override
public CompletableFuture<Either<List<CompletionItem>, CompletionList>> completion(CompletionParams params) {
return computeDOMAsync(params.getTextDocument(), (cancelChecker, xmlDocument) -> {
CompletionList list = getXMLLanguageService().doComplete(xmlDocument, params.getPosition(), sharedSettings,
cancelChecker);
return Either.forRight(list);
});
}
@Override
public CompletableFuture<Hover> hover(HoverParams params) {
return computeDOMAsync(params.getTextDocument(), (cancelChecker, xmlDocument) -> {
return getXMLLanguageService().doHover(xmlDocument, params.getPosition(), sharedSettings, cancelChecker);
});
}
/**
* Returns the indentation settings (`xml.format.tabSize` and
* `xml.format.insertSpaces`) for the document with the given URI.
*
* @param uri the uri of the document to get the indentation settings for
* @return the indentation settings (`xml.format.tabSize` and
* `xml.format.insertSpaces`) for the document with the given URI
*/
private CompletableFuture<XMLFormattingOptions> getIndentationSettings(@NonNull String uri) {
ConfigurationItem insertSpaces = new ConfigurationItem();
insertSpaces.setScopeUri(uri);
insertSpaces.setSection("xml.format.insertSpaces");
ConfigurationItem tabSize = new ConfigurationItem();
tabSize.setScopeUri(uri);
tabSize.setSection("xml.format.tabSize");
return xmlLanguageServer.getLanguageClient().configuration(new ConfigurationParams(Arrays.asList( //
insertSpaces, tabSize //
))).thenApply(indentationSettings -> {
XMLFormattingOptions newOptions = new XMLFormattingOptions();
if (indentationSettings.get(0) != null) {
newOptions.setInsertSpaces(((JsonPrimitive) indentationSettings.get(0)).getAsBoolean());
}
if (indentationSettings.get(1) != null) {
newOptions.setTabSize(((JsonPrimitive) indentationSettings.get(1)).getAsInt());
}
return newOptions;
});
}
@Override
public CompletableFuture<List<? extends DocumentHighlight>> documentHighlight(DocumentHighlightParams params) {
return computeDOMAsync(params.getTextDocument(), (cancelChecker, xmlDocument) -> {
return getXMLLanguageService().findDocumentHighlights(xmlDocument, params.getPosition(), cancelChecker);
});
}
@Override
public CompletableFuture<List<Either<SymbolInformation, DocumentSymbol>>> documentSymbol(
DocumentSymbolParams params) {
TextDocument document = getDocument(params.getTextDocument().getUri());
if (document == null) {
return CompletableFuture.completedFuture(null);
}
XMLSymbolSettings symbolSettings = sharedSettings.getSymbolSettings();
if (!symbolSettings.isEnabled() || symbolSettings.isExcluded(document.getUri())) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
return computeDOMAsync(params.getTextDocument(), (cancelChecker, xmlDocument) -> {
boolean resultLimitExceeded = false;
List<Either<SymbolInformation, DocumentSymbol>> symbols = null;
if (hierarchicalDocumentSymbolSupport) {
DocumentSymbolsResult result = getXMLLanguageService().findDocumentSymbols(xmlDocument, symbolSettings,
cancelChecker);
resultLimitExceeded = result.isResultLimitExceeded();
symbols = result //
.stream() //
.map(s -> {
Either<SymbolInformation, DocumentSymbol> e = Either.forRight(s);
return e;
}) //
.collect(Collectors.toList());
} else {
SymbolInformationResult result = getXMLLanguageService().findSymbolInformations(xmlDocument,
symbolSettings, cancelChecker);
resultLimitExceeded = result.isResultLimitExceeded();
symbols = result.stream() //
.map(s -> {
Either<SymbolInformation, DocumentSymbol> e = Either.forLeft(s);
return e;
}) //
.collect(Collectors.toList());
}
if (resultLimitExceeded) {
// send warning
getLimitExceededWarner().onResultLimitExceeded(xmlDocument.getTextDocument().getUri(),
LimitFeature.SYMBOLS);
}
return symbols;
});
}
@Override
public CompletableFuture<List<? extends TextEdit>> formatting(DocumentFormattingParams params) {
return computeAsync((cancelChecker) -> {
String uri = params.getTextDocument().getUri();
TextDocument document = getDocument(uri);
if (document == null) {
return null;
}
CompositeSettings settings = new CompositeSettings(getSharedSettings(), params.getOptions());
return getXMLLanguageService().format(document, null, settings);
});
}
@Override
public CompletableFuture<List<? extends TextEdit>> rangeFormatting(DocumentRangeFormattingParams params) {
return computeAsync((cancelChecker) -> {
String uri = params.getTextDocument().getUri();
TextDocument document = getDocument(uri);
if (document == null) {
return null;
}
CompositeSettings settings = new CompositeSettings(getSharedSettings(), params.getOptions());
return getXMLLanguageService().format(document, params.getRange(), settings);
});
}
@Override
public CompletableFuture<WorkspaceEdit> rename(RenameParams params) {
return computeDOMAsync(params.getTextDocument(), (cancelChecker, xmlDocument) -> {
return getXMLLanguageService().doRename(xmlDocument, params.getPosition(), params.getNewName());
});
}
@Override
public void didOpen(DidOpenTextDocumentParams params) {
TextDocument document = documents.onDidOpenTextDocument(params);
triggerValidationFor(document, TriggeredBy.didOpen);
}
/**
* This method is triggered when the user types on an XML document.
*/
@Override
public void didChange(DidChangeTextDocumentParams params) {
TextDocument document = documents.onDidChangeTextDocument(params);
triggerValidationFor(document, TriggeredBy.didChange, params.getContentChanges());
}
@Override
public void didClose(DidCloseTextDocumentParams params) {
TextDocumentIdentifier identifier = params.getTextDocument();
String uri = identifier.getUri();
DOMDocument xmlDocument = getNowDOMDocument(uri);
// Remove the document from the cache
documents.onDidCloseTextDocument(params);
// Publish empty errors from the document
xmlLanguageServer.getLanguageClient()
.publishDiagnostics(new PublishDiagnosticsParams(uri, Collections.emptyList()));
getLimitExceededWarner().evictValue(uri);
// Manage didClose document lifecycle participants
if (xmlDocument != null) {
getXMLLanguageService().getDocumentLifecycleParticipants().forEach(participant -> {
try {
participant.didClose(xmlDocument);
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "Error while processing didClose for the participant '"
+ participant.getClass().getName() + "'.", e);
}
});
}
}
private DOMDocument getNowDOMDocument(String uri) {
TextDocument document = documents.get(uri);
if (document != null) {
return ((ModelTextDocument<DOMDocument>) document).getModel().getNow(null);
}
return null;
}
@Override
public CompletableFuture<List<FoldingRange>> foldingRange(FoldingRangeRequestParams params) {
return computeDOMAsync(params.getTextDocument(), (cancelChecker, xmlDocument) -> {
return getXMLLanguageService().getFoldingRanges(xmlDocument, sharedSettings.getFoldingSettings(),
cancelChecker);
});
}
@Override
public CompletableFuture<List<DocumentLink>> documentLink(DocumentLinkParams params) {
return computeDOMAsync(params.getTextDocument(), (cancelChecker, xmlDocument) -> {
return getXMLLanguageService().findDocumentLinks(xmlDocument);
});
}
@Override
public CompletableFuture<Either<List<? extends Location>, List<? extends LocationLink>>> definition(
DefinitionParams params) {
return computeDOMAsync(params.getTextDocument(), (cancelChecker, xmlDocument) -> {
if (definitionLinkSupport) {
return Either.forRight(
getXMLLanguageService().findDefinition(xmlDocument, params.getPosition(), cancelChecker));
}
List<? extends Location> locations = getXMLLanguageService()
.findDefinition(xmlDocument, params.getPosition(), cancelChecker) //
.stream() //
.map(locationLink -> XMLPositionUtility.toLocation(locationLink)) //
.collect(Collectors.toList());
return Either.forLeft(locations);
});
}
@Override
public CompletableFuture<Either<List<? extends Location>, List<? extends LocationLink>>> typeDefinition(
TypeDefinitionParams params) {
return computeDOMAsync(params.getTextDocument(), (cancelChecker, xmlDocument) -> {
if (typeDefinitionLinkSupport) {
return Either.forRight(
getXMLLanguageService().findTypeDefinition(xmlDocument, params.getPosition(), cancelChecker));
}
List<? extends Location> locations = getXMLLanguageService()
.findTypeDefinition(xmlDocument, params.getPosition(), cancelChecker) //
.stream() //
.map(locationLink -> XMLPositionUtility.toLocation(locationLink)) //
.collect(Collectors.toList());
return Either.forLeft(locations);
});
}
@Override
public CompletableFuture<List<? extends Location>> references(ReferenceParams params) {
return computeDOMAsync(params.getTextDocument(), (cancelChecker, xmlDocument) -> {
return getXMLLanguageService().findReferences(xmlDocument, params.getPosition(), params.getContext(),
cancelChecker);
});
}
@Override
public CompletableFuture<List<? extends CodeLens>> codeLens(CodeLensParams params) {
if (!sharedSettings.getCodeLensSettings().isEnabled()) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
return computeDOMAsync(params.getTextDocument(), (cancelChecker, xmlDocument) -> {
return getXMLLanguageService().getCodeLens(xmlDocument, sharedSettings.getCodeLensSettings(),
cancelChecker);
});
}
@Override
public CompletableFuture<List<Either<Command, CodeAction>>> codeAction(CodeActionParams params) {
String uri = params.getTextDocument().getUri();
return getIndentationSettings(uri) //
.handle((XMLFormattingOptions indentationSettings, Throwable err) -> {
if (indentationSettings != null) {
sharedSettings.getFormattingSettings().merge(indentationSettings);
}
return null;
}) //
.thenCombine(computeDOMAsync(params.getTextDocument(), (cancelChecker, xmlDocument) -> {
return xmlDocument;
}), (void_, xmlDocument) -> {
return (List<Either<Command, CodeAction>>) getXMLLanguageService()
.doCodeActions(params.getContext(), params.getRange(), xmlDocument, sharedSettings) //
.stream() //
.map(ca -> {
if (codeActionLiteralSupport) {
Either<Command, CodeAction> e = Either.forRight(ca);
return e;
} else {
List<Object> arguments = Arrays.asList(uri,
xmlDocument.getTextDocument().getVersion(),
ca.getEdit().getDocumentChanges().get(0).getLeft().getEdits());
Command command = new Command(ca.getTitle(), "_xml.applyCodeAction", arguments);
Either<Command, CodeAction> e = Either.forLeft(command);
return e;
}
}) //
.collect(Collectors.toList());
});
}
@Override
public CompletableFuture<List<SelectionRange>> selectionRange(SelectionRangeParams params) {
return computeDOMAsync(params.getTextDocument(), (cancelChecker, xmlDocument) -> {
return getXMLLanguageService().getSelectionRanges(xmlDocument, params.getPositions(), cancelChecker);
});
}
public CompletableFuture<LinkedEditingRanges> linkedEditingRange(LinkedEditingRangeParams params) {
return computeDOMAsync(params.getTextDocument(), (cancelChecker, xmlDocument) -> {
return getXMLLanguageService().findLinkedEditingRanges(xmlDocument, params.getPosition(), cancelChecker);
});
}
@Override
public void didSave(DidSaveTextDocumentParams params) {
computeAsync((monitor) -> {
// A document was saved, collect documents to revalidate
SaveContext context = new SaveContext(params.getTextDocument().getUri());
doSave(context);
// Manage didSave document lifecycle participants
final DOMDocument xmlDocument = getNowDOMDocument(params.getTextDocument().getUri());
if (xmlDocument != null) {
getXMLLanguageService().getDocumentLifecycleParticipants().forEach(participant -> {
try {
participant.didSave(xmlDocument);
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "Error while processing didSave for the participant '"
+ participant.getClass().getName() + "'.", e);
}
});
}
return null;
});
}
/**
* Update settings of the language service.
*
* @param settings
*/
public void updateSettings(Object settings) {
SaveContext context = new SaveContext(settings);
doSave(context);
}
void doSave(String uri) {
SaveContext context = new SaveContext(uri);
doSave(context);
}
/**
* Save settings or XML file.
*
* @param context
*/
void doSave(SaveContext context) {
getXMLLanguageService().doSave(context);
context.triggerValidationIfNeeded();
}
private void triggerValidationFor(Collection<ModelTextDocument<DOMDocument>> documents) {
if (!documents.isEmpty()) {
xmlLanguageServer.schedule(() -> {
documents.forEach(document -> {
try {
validate(document.getModel().getNow(null));
} catch (CancellationException e) {
// Ignore the error and continue to validate other documents
}
});
}, 500, TimeUnit.MILLISECONDS);
}
}
private void triggerValidationFor(TextDocument document, TriggeredBy triggeredBy) {
triggerValidationFor(document, triggeredBy, null);
}
@SuppressWarnings("unchecked")
private void triggerValidationFor(TextDocument document, TriggeredBy triggeredBy,
List<TextDocumentContentChangeEvent> changeEvents) {
((ModelTextDocument<DOMDocument>) document).getModel()//
.thenAcceptAsync(xmlDocument -> {
// Validate the DOM document
validate(xmlDocument);
// Manage didOpen, didChange document lifecycle participants
switch (triggeredBy) {
case didOpen:
getXMLLanguageService().getDocumentLifecycleParticipants().forEach(participant -> {
try {
participant.didOpen(xmlDocument);
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "Error while processing didOpen for the participant '"
+ participant.getClass().getName() + "'.", e);
}
});
break;
case didChange:
getXMLLanguageService().getDocumentLifecycleParticipants().forEach(participant -> {
try {
participant.didChange(xmlDocument);
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "Error while processing didChange for the participant '"
+ participant.getClass().getName() + "'.", e);
}
});
break;
default:
// Do nothing
}
});
}
void validate(DOMDocument xmlDocument) throws CancellationException {
CancelChecker cancelChecker = xmlDocument.getCancelChecker();
cancelChecker.checkCanceled();
getXMLLanguageService().publishDiagnostics(xmlDocument,
params -> xmlLanguageServer.getLanguageClient().publishDiagnostics(params),
(doc) -> triggerValidationFor(doc, TriggeredBy.Other), sharedSettings.getValidationSettings(),
cancelChecker);
}
private XMLLanguageService getXMLLanguageService() {
return xmlLanguageServer.getXMLLanguageService();
}
public void updateCompletionSettings(XMLCompletionSettings newCompletion) {
sharedSettings.getCompletionSettings().merge(newCompletion);
}
public void updateSymbolSettings(XMLSymbolSettings newSettings) {
sharedSettings.getSymbolSettings().merge(newSettings);
}
public void updateCodeLensSettings(XMLCodeLensSettings newSettings) {
sharedSettings.getCodeLensSettings().merge(newSettings);
}
public void updatePreferences(XMLPreferences newPreferences) {
sharedSettings.getPreferences().merge(newPreferences);
}
public XMLSymbolSettings getSharedSymbolSettings() {
return sharedSettings.getSymbolSettings();
}
public XMLCodeLensSettings getSharedCodeLensSettings() {
return sharedSettings.getCodeLensSettings();
}
public boolean isIncrementalSupport() {
return documents.isIncremental();
}
public XMLFormattingOptions getSharedFormattingSettings() {
return sharedSettings.getFormattingSettings();
}
public XMLValidationSettings getValidationSettings() {
return sharedSettings.getValidationSettings();
}
public XMLPreferences getPreferences() {
return sharedSettings.getPreferences();
}
public SharedSettings getSharedSettings() {
return this.sharedSettings;
}
/**
* Returns the text document from the given uri.
*
* @param uri the uri
* @return the text document from the given uri.
*/
public ModelTextDocument<DOMDocument> getDocument(String uri) {
return documents.get(uri);
}
public Collection<ModelTextDocument<DOMDocument>> allDocuments() {
return documents.all();
}
public boolean documentIsOpen(String uri) {
ModelTextDocument<DOMDocument> document = getDocument(uri);
return document != null;
}
/**
* Compute the DOM Document for a given uri in a future and then apply the given
* function.
*
* @param <R>
* @param documentIdentifier the document indetifier.
* @param code a bi function that accepts a {@link CancelChecker}
* and parsed {@link DOMDocument} and returns the to
* be computed value
* @return the DOM Document for a given uri in a future and then apply the given
* function.
*/
public <R> CompletableFuture<R> computeDOMAsync(TextDocumentIdentifier documentIdentifier,
BiFunction<CancelChecker, DOMDocument, R> code) {
ModelTextDocument<DOMDocument> document = getDocument(documentIdentifier.getUri());
if (document != null) {
return computeModelAsync(document.getModel(), code);
}
return CompletableFuture.completedFuture(null);
}
private static <R, M> CompletableFuture<R> computeModelAsync(CompletableFuture<M> loadModel,
BiFunction<CancelChecker, M, R> code) {
CompletableFuture<CancelChecker> start = new CompletableFuture<>();
CompletableFuture<R> result = start.thenCombineAsync(loadModel, code);
CancelChecker cancelIndicator = () -> {
if (result.isCancelled())
throw new CancellationException();
};
start.complete(cancelIndicator);
return result;
}
public LimitExceededWarner getLimitExceededWarner() {
if (this.limitExceededWarner == null) {
this.limitExceededWarner = new LimitExceededWarner(this.xmlLanguageServer);
}
return this.limitExceededWarner;
}
}
@@ -1,131 +0,0 @@
/**
* Copyright (c) 2018 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
* http://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
*/
package org.eclipse.lemminx;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import org.eclipse.lemminx.services.extensions.commands.IXMLCommandService;
import org.eclipse.lsp4j.DidChangeConfigurationParams;
import org.eclipse.lsp4j.DidChangeWatchedFilesParams;
import org.eclipse.lsp4j.DidChangeWorkspaceFoldersParams;
import org.eclipse.lsp4j.ExecuteCommandParams;
import org.eclipse.lsp4j.FileEvent;
import org.eclipse.lsp4j.SymbolInformation;
import org.eclipse.lsp4j.WorkspaceSymbolParams;
import org.eclipse.lsp4j.jsonrpc.CompletableFutures;
import org.eclipse.lsp4j.jsonrpc.ResponseErrorException;
import org.eclipse.lsp4j.jsonrpc.messages.ResponseError;
import org.eclipse.lsp4j.jsonrpc.messages.ResponseErrorCode;
import org.eclipse.lsp4j.services.WorkspaceService;
/**
* XML workspace service.
*
*/
public class XMLWorkspaceService implements WorkspaceService, IXMLCommandService {
private final XMLLanguageServer xmlLanguageServer;
private final Map<String, IDelegateCommandHandler> commands;
public XMLWorkspaceService(XMLLanguageServer xmlLanguageServer) {
this.xmlLanguageServer = xmlLanguageServer;
this.commands = new HashMap<>();
}
@Override
public CompletableFuture<List<? extends SymbolInformation>> symbol(WorkspaceSymbolParams params) {
return null;
}
@Override
public CompletableFuture<Object> executeCommand(ExecuteCommandParams params) {
synchronized (commands) {
IDelegateCommandHandler handler = commands.get(params.getCommand());
if (handler == null) {
throw new ResponseErrorException(new ResponseError(ResponseErrorCode.InternalError,
"No command handler for the command: " + params.getCommand(), null));
}
return CompletableFutures.computeAsync(cancelChecker -> {
try {
return handler.executeCommand(params, xmlLanguageServer.getSharedSettings(), cancelChecker);
} catch (Exception e) {
if (e instanceof ResponseErrorException) {
throw (ResponseErrorException) e;
} else if (e instanceof CancellationException) {
throw (CancellationException) e;
}
throw new ResponseErrorException(
new ResponseError(ResponseErrorCode.UnknownErrorCode, e.getMessage(), e));
}
});
}
}
@Override
public void didChangeConfiguration(DidChangeConfigurationParams params) {
xmlLanguageServer.updateSettings(params.getSettings());
xmlLanguageServer.getCapabilityManager().syncDynamicCapabilitiesWithPreferences();
}
@Override
public void didChangeWorkspaceFolders(DidChangeWorkspaceFoldersParams params) {
xmlLanguageServer.getXMLLanguageService().getWorkspaceServiceParticipants().forEach(participant -> participant.didChangeWorkspaceFolders(params));
}
@Override
public void didChangeWatchedFiles(DidChangeWatchedFilesParams params) {
XMLTextDocumentService xmlTextDocumentService = (XMLTextDocumentService) xmlLanguageServer
.getTextDocumentService();
List<FileEvent> changes = params.getChanges();
for (FileEvent change : changes) {
if (!xmlTextDocumentService.documentIsOpen(change.getUri())) {
xmlTextDocumentService.doSave(change.getUri());
}
}
}
@Override
public void registerCommand(String commandId, IDelegateCommandHandler handler) {
synchronized (commands) {
if (commands.containsKey(commandId)) {
throw new IllegalArgumentException("Command with id '" + commandId + "' is already registered");
}
commands.put(commandId, handler);
}
}
@Override
public void unregisterCommand(String commandId) {
synchronized (commands) {
commands.remove(commandId);
}
}
@Override
public CompletableFuture<Object> executeClientCommand(ExecuteCommandParams command) {
return xmlLanguageServer.getLanguageClient().executeClientCommand(command);
}
@Override
public void endCommandsRegistration() {
if (!commands.isEmpty()) {
xmlLanguageServer.getCapabilityManager().registerExecuteCommand(new ArrayList<>(commands.keySet()));
}
}
}
@@ -1,122 +0,0 @@
/*******************************************************************************
* Copyright (c) 2020 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
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx.client;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.eclipse.lemminx.services.IXMLNotificationService;
import org.eclipse.lemminx.settings.SharedSettings;
import org.eclipse.lsp4j.Command;
import org.eclipse.lsp4j.MessageType;
/** Sends a notification to the client via a jsonrpc notification
* while managing cache
*/
public abstract class AbstractXMLNotifier {
private final IXMLNotificationService notificationService;
protected final Map<IXMLSettingFeature /* feature name */, Set<String>> cache;
public AbstractXMLNotifier(IXMLNotificationService notificationService) {
this.notificationService = notificationService;
this.cache = new HashMap<>();
}
/**
* Returns true if <code>value</code> exists in the cache set
* identified by <code>key</code> and false otherwise
*
* @param key the key for the cache set
* @param value the value to check
* @return true if <code>value</code> exists in the cache set
* identified by <code>key</code> and false otherwise
*/
public boolean existsInCache(IXMLSettingFeature key, String value) {
return this.cache.get(key) != null && this.cache.get(key).contains(value);
}
/**
* Returns true if <code>values</code> is equal to the cache set
* identified by <code>key</code> and false otherwise
*
* @param key the key for the cache set
* @param values the values set to check
* @return true if <code>values</code> is equal to the cache set
* identified by <code>key</code> and false otherwise
*/
public boolean existsInCache(IXMLSettingFeature key, Set<String> values) {
return this.cache.get(key) != null && this.cache.get(key).equals(values);
}
/**
* Adds <code>value</code> to the cache set identified by <code>key</code>
*
* If such cache set does not exist, this method will initialize it with
* <code>value</code> added
*
* @param key the key for the cache set to add to
* @param value the value to add
*/
public void addToCache(IXMLSettingFeature key, String value) {
if (this.cache.get(key) == null) {
this.cache.put(key, new HashSet<String>());
}
this.cache.get(key).add(value);
}
/**
* Sets the cache set identified by <code>key</code>
*
* @param key the key for the cache set
* @param values the cache set
*/
public void setCacheValues(IXMLSettingFeature key, Set<String> values) {
evictKey(key);
this.cache.put(key, new HashSet<String>(values));
}
/**
* Evicts the set identified by the provided key, from the cache
*
* @param key
*/
public void evictKey(IXMLSettingFeature key) {
Set<String> setToEvict = this.cache.get(key);
if (setToEvict != null) {
setToEvict.clear();
}
}
/**
* Evicts the provided value from all cached sets
*
* @param value
*/
public void evictValue(String value) {
for (Set<String> set: this.cache.values()) {
set.remove(value);
}
}
protected void sendNotification(String message, MessageType messageType, Command... commands) {
notificationService.sendNotification(message, messageType, commands);
}
protected SharedSettings getSharedSettings() {
return notificationService.getSharedSettings();
}
}
@@ -1,46 +0,0 @@
/*******************************************************************************
* Copyright (c) 2019-2020 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
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx.client;
/**
* Commonly used client commands
*
* @author Angelo ZERR
*
*/
public class ClientCommands {
private ClientCommands() {
}
/**
* Show references
*/
public static final String SHOW_REFERENCES = "xml.show.references";
/**
* Open settings command. This custom command is sent to the client in order to
* have the client open its settings UI.
*/
public static final String OPEN_SETTINGS = "xml.open.settings";
/**
* Open the binding wizard to bind the XML to a grammar/schema.
*/
public static final String OPEN_BINDING_WIZARD = "xml.open.binding.wizard";
/**
* Client command to open URI
*/
public static final String OPEN_URI = "xml.open.uri";
}
@@ -1,33 +0,0 @@
/*******************************************************************************
* Copyright (c) 2019 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
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx.client;
/**
* CodeLens kind supported by the client.
*
* @author Angelo ZERR
*
* @see https://github.com/microsoft/language-server-protocol/issues/788
*/
public class CodeLensKind {
private CodeLensKind() {
}
public static final String References = "references";
public static final String Implementations = "implementations";
public static final String Association = "association";
public static final String OpenUri = "open.uri";
}
@@ -1,104 +0,0 @@
/*******************************************************************************
* Copyright (c) 2019 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
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx.client;
import java.util.List;
import org.eclipse.xtext.xbase.lib.Pure;
import org.eclipse.xtext.xbase.lib.util.ToStringBuilder;
/**
* Specific capabilities for the `CodeLensKind`.
*
* @see https://github.com/microsoft/language-server-protocol/issues/788
*/
@SuppressWarnings("all")
public class CodeLensKindCapabilities {
/**
* The codeLens kind values the client supports. When this property exists the
* client also guarantees that it will handle values outside its set gracefully
* and falls back to a default value when unknown.
*
* If this property is not present the client only supports the codeLens kinds
* from `File` to `Array` as defined in the initial version of the protocol.
*/
private List<String> valueSet;
public CodeLensKindCapabilities() {
}
public CodeLensKindCapabilities(final List<String> valueSet) {
this.valueSet = valueSet;
}
/**
* The codeLens kind values the client supports. When this property exists the
* client also guarantees that it will handle values outside its set gracefully
* and falls back to a default value when unknown.
*
* If this property is not present the client only supports the codeLens kinds
* from `File` to `Array` as defined in the initial version of the protocol.
*/
@Pure
public List<String> getValueSet() {
return this.valueSet;
}
/**
* The codeLens kind values the client supports. When this property exists the
* client also guarantees that it will handle values outside its set gracefully
* and falls back to a default value when unknown.
*
* If this property is not present the client only supports the codeLens kinds
* from `File` to `Array` as defined in the initial version of the protocol.
*/
public void setValueSet(final List<String> valueSet) {
this.valueSet = valueSet;
}
@Override
@Pure
public String toString() {
ToStringBuilder b = new ToStringBuilder(this);
b.add("valueSet", this.valueSet);
return b.toString();
}
@Override
@Pure
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
CodeLensKindCapabilities other = (CodeLensKindCapabilities) obj;
if (this.valueSet == null) {
if (other.valueSet != null) {
return false;
}
} else if (!this.valueSet.equals(other.valueSet)) {
return false;
}
return true;
}
@Override
@Pure
public int hashCode() {
return 31 * 1 + ((this.valueSet == null) ? 0 : this.valueSet.hashCode());
}
}
@@ -1,120 +0,0 @@
/*******************************************************************************
* Copyright (c) 2019 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
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx.client;
/**
* Extended client capabilities not defined by the LSP.
*
* @author Angelo ZERR
*
* @see https://github.com/microsoft/language-server-protocol/issues/788
*/
public class ExtendedClientCapabilities {
private ExtendedCodeLensCapabilities codeLens;
private boolean actionableNotificationSupport;
private boolean openSettingsCommandSupport;
private boolean bindingWizardSupport;
private boolean shouldLanguageServerExitOnShutdown;
public ExtendedCodeLensCapabilities getCodeLens() {
return codeLens;
}
public void setCodeLens(ExtendedCodeLensCapabilities codeLens) {
this.codeLens = codeLens;
}
/**
* Returns true if the client supports actionable notifications and false otherwise
*
* See {@link org.eclipse.lemminx.customservice.ActionableNotification} and
* {@link org.eclipse.lemminx.customservice.XMLLanguageClientAPI#actionableNotification}
*
* @return true if the client supports actionable notifications and false otherwise
*/
public boolean isActionableNotificationSupport() {
return actionableNotificationSupport;
}
/**
* Sets the actionableNotificationSupport boolean
*
* @param actionableNotificationSupport
*/
public void setActionableNotificationSupport(boolean actionableNotificationSupport) {
this.actionableNotificationSupport = actionableNotificationSupport;
}
/**
* Returns true if the client supports the open settings command and false otherwise
*
* See {@link org.eclipse.lemminx.client.ClientCommands#OPEN_SETTINGS}
*
* @return true if the client supports the open settings command and false otherwise
*/
public boolean isOpenSettingsCommandSupport() {
return openSettingsCommandSupport;
}
/**
* Sets the openSettingsCommandSupport boolean
*
* @param openSettingsCommandSupport
*/
public void setOpenSettingsCommandSupport(boolean openSettingsCommandSupport) {
this.openSettingsCommandSupport = openSettingsCommandSupport;
}
/**
* Returns true if the client supports the `xml.open.binding.wizard` command using dropdown and false otherwise
*
* @return bindingWizardSupport
*/
public boolean isBindingWizardSupport() {
return this.bindingWizardSupport;
}
/**
* Sets the bindingWizardSupport boolean
*
* @param bindingWizardSupport
*/
public void setBindingWizardSupport(boolean bindingWizardSupport) {
this.bindingWizardSupport = bindingWizardSupport;
}
/**
* Sets the boolean permitting language server to exit on client
* shutdown() request, without waiting for client to call exit()
*
* @param shouldLanguageServerExitOnShutdown
*/
public void setShouldLanguageServerExitOnShutdown(boolean shouldLanguageServerExitOnShutdown) {
this.shouldLanguageServerExitOnShutdown = shouldLanguageServerExitOnShutdown;
}
/**
* Returns true if the client should exit on shutdown() request and
* avoid waiting for an exit() request
*
* @return true if the language server should exit on shutdown() request
*/
public boolean shouldLanguageServerExitOnShutdown() {
return shouldLanguageServerExitOnShutdown;
}
}
@@ -1,108 +0,0 @@
/*******************************************************************************
* Copyright (c) 2019 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
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx.client;
import org.eclipse.lsp4j.DynamicRegistrationCapabilities;
import org.eclipse.xtext.xbase.lib.Pure;
import org.eclipse.xtext.xbase.lib.util.ToStringBuilder;
/**
* Extended capabilities specific to the `textDocument/codeLens` request. This capability doesn't belong to LSP specification. See proposal at
* https://github.com/microsoft/language-server-protocol/issues/788
*
* @author Angelo ZERR
*
* @see https://github.com/microsoft/language-server-protocol/issues/788
*/
@SuppressWarnings("all")
public class ExtendedCodeLensCapabilities extends DynamicRegistrationCapabilities {
/**
* Specific capabilities for the `CodeLensKind` in the `textDocument/codeLens`
* request.
*/
private CodeLensKindCapabilities codeLensKind;
public ExtendedCodeLensCapabilities() {
}
public ExtendedCodeLensCapabilities(final Boolean dynamicRegistration) {
super(dynamicRegistration);
}
public ExtendedCodeLensCapabilities(final CodeLensKindCapabilities codeLensKind) {
this.codeLensKind = codeLensKind;
}
public ExtendedCodeLensCapabilities(final CodeLensKindCapabilities codeLensKind,
final Boolean dynamicRegistration) {
super(dynamicRegistration);
this.codeLensKind = codeLensKind;
}
/**
* Specific capabilities for the `CodeLensKind` in the `textDocument/codeLens`
* request.
*/
@Pure
public CodeLensKindCapabilities getCodeLensKind() {
return this.codeLensKind;
}
/**
* Specific capabilities for the `CodeLensKind` in the `textDocument/codeLens`
* request.
*/
public void setCodeLensKind(final CodeLensKindCapabilities codeLensKind) {
this.codeLensKind = codeLensKind;
}
@Override
@Pure
public String toString() {
ToStringBuilder b = new ToStringBuilder(this);
b.add("codeLensKind", this.codeLensKind);
b.add("dynamicRegistration", getDynamicRegistration());
return b.toString();
}
@Override
@Pure
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
if (!super.equals(obj)) {
return false;
}
ExtendedCodeLensCapabilities other = (ExtendedCodeLensCapabilities) obj;
if (this.codeLensKind == null) {
if (other.codeLensKind != null) {
return false;
}
} else if (!this.codeLensKind.equals(other.codeLensKind)) {
return false;
}
return true;
}
@Override
@Pure
public int hashCode() {
return 31 * super.hashCode() + ((this.codeLensKind == null) ? 0 : this.codeLensKind.hashCode());
}
}
@@ -1,31 +0,0 @@
/*******************************************************************************
* Copyright (c) 2020 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
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx.client;
/**
* Features that are associated with a particuliar settingId
*/
public interface IXMLSettingFeature {
/**
* Returns a displayable name for this feature
*
* @return a displayable name for this feature
*/
public String getName();
/**
* Returns the related settingId for this feature
*
* @return the related settingId for this feature
*/
public String getSettingId();
}
@@ -1,81 +0,0 @@
/*******************************************************************************
* Copyright (c) 2020 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
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx.client;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.lemminx.services.IXMLNotificationService;
import org.eclipse.lsp4j.Command;
import org.eclipse.lsp4j.MessageType;
/**
* This class sends a warning to the client via a jsonrpc notification
*
* The cache is a Map<String, Set<String>>
*
* where the keys are the feature names and the value is a set of file paths for
* which notifications have been sent already
*/
public class InvalidPathWarner extends AbstractXMLNotifier {
public InvalidPathWarner(IXMLNotificationService notificationService ) {
super(notificationService);
}
/**
* Sends the invalid path warning to the client only if the provided
* <code>invalidPaths</code> set does not match the <code>feature</code>'s cache set
*
* After sending the warning, the <code>feature</code>'s cache set is replaced
* with the provided <code>invalidPaths</code> set
*
* @param invalidPaths the set of invalid paths
* @param feature the feature
*/
public void onInvalidFilePath(Set<String> invalidPaths, PathFeature feature) {
if (existsInCache(feature, invalidPaths)) {
return;
}
sendInvalidFilePathWarning(invalidPaths, feature);
setCacheValues(feature, new HashSet<String>(invalidPaths));
}
private void sendInvalidFilePathWarning(Set<String> invalidPaths, PathFeature feature) {
String message = createWarningMessage(feature.getSettingId(), invalidPaths);
Command command = new Command("Configure setting", ClientCommands.OPEN_SETTINGS,
Collections.singletonList(feature.getSettingId()));
super.sendNotification(message, MessageType.Error, command);
}
private static String createWarningMessage(String settingId, Set<String> invalidPaths) {
StringBuilder message = new StringBuilder("Invalid path");
if (invalidPaths.size() > 1) {
message.append('s');
}
message.append(" for setting '").append(settingId).append("': ");
List<String> list = new ArrayList<>(invalidPaths);
for (int i = 0; i < list.size(); i++) {
if (i != 0) {
message.append(", ");
}
message.append("'").append(list.get(i)).append("'");
}
return message.toString();
}
}
@@ -1,92 +0,0 @@
/*******************************************************************************
* Copyright (c) 2020 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
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx.client;
import java.net.URI;
import java.nio.file.Paths;
import java.util.Collections;
import org.eclipse.lemminx.services.IXMLNotificationService;
import org.eclipse.lsp4j.Command;
import org.eclipse.lsp4j.MessageType;
/**
* LimitExceededWarner sends a warning to the client via a jsonrpc notification
*
* Cache is a Map<String, Set<String>>
*
* where the keys are the feature names and the values are the file paths for
* which notifications have been sent already
*/
public class LimitExceededWarner extends AbstractXMLNotifier {
public LimitExceededWarner(IXMLNotificationService notificationService) {
super(notificationService);
}
/**
* Sends the limit exceeded warning to the client only if the provided
* uri does not exist in the <code>feature</code>'s cache set
*
* After sending the warning, the uri is added to the <code>feature</code>'s cache set
*
* @param uri the file uri
* @param featureName the feature name
*/
public void onResultLimitExceeded(String uri, LimitFeature feature) {
int resultLimit = 0;
switch(feature) {
case SYMBOLS:
resultLimit = getSharedSettings().getSymbolSettings().getMaxItemsComputed();
}
onResultLimitExceeded(uri, resultLimit, feature);
}
/**
* Sends the limit exceeded warning to the client only if the provided
* uri does not exist in the cache
*
* After sending the warning, the uri is added to the cache
*
* @param uri the file uri
* @param resultLimit the result limit
* @param feature the feature
*/
public void onResultLimitExceeded(String uri, int resultLimit, LimitFeature feature) {
if (existsInCache(feature, uri)) {
return;
}
sendLimitExceededWarning(uri, resultLimit, feature);
addToCache(feature, uri);
}
/**
* Send limit exceeded warning to client
*
* @param uri the file uri
* @param resultLimit the result limit
* @param feature the feature
*/
private void sendLimitExceededWarning(String uri, int resultLimit, LimitFeature feature) {
String filename = Paths.get(URI.create(uri)).getFileName().toString();
String message = filename != null ? filename + ": " : "";
message += "For performance reasons, " + feature.getName() + " have been limited to " + resultLimit
+ " items.\nIf a new limit is set, please close and reopen this file to recompute " + feature.getName() + ".";
// create command that opens the settings UI on the client side, in order to
// quickly edit the setting
Command command = new Command("Configure limit", ClientCommands.OPEN_SETTINGS,
Collections.singletonList(feature.getSettingId()));
super.sendNotification(message, MessageType.Info ,command);
}
}
@@ -1,41 +0,0 @@
/*******************************************************************************
* Copyright (c) 2020 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
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx.client;
/**
* Features that are associated with some limit
*
* For example, document symbols have a maximum limit for performance reasons
*/
public enum LimitFeature implements IXMLSettingFeature {
SYMBOLS("document symbols", "xml.symbols.maxItemsComputed");
private final String name;
private final String settingId;
private LimitFeature(String name, String settingId) {
this.name = name;
this.settingId = settingId;
}
@Override
public String getName() {
return name;
}
@Override
public String getSettingId() {
return settingId;
}
}
@@ -1,40 +0,0 @@
/*******************************************************************************
* Copyright (c) 2020 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
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx.client;
/**
* Features that are associated with filePaths
*
* For example, XML catalog support is associated with XML catalog filePaths
*/
public enum PathFeature implements IXMLSettingFeature {
CATALOGS("XML catalog", "xml.catalogs");
private final String name;
private final String settingId;
private PathFeature(String name, String settingId) {
this.name = name;
this.settingId = settingId;
}
@Override
public String getName() {
return name;
}
@Override
public String getSettingId() {
return settingId;
}
}
@@ -1,51 +0,0 @@
/*******************************************************************************
* Copyright (c) 2000, 2005 IBM Corporation 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx.commons;
/**
* Indicates the attempt to access a non-existing position. The attempt has been
* performed on a text store such as a document or string.
* <p>
* This class is not intended to be serialized.
* </p>
*
* This class is a copy/paste of org.eclipse.jface.text.BadLocationException.
*/
public class BadLocationException extends Exception {
/**
* Serial version UID for this class.
* <p>
* Note: This class is not intended to be serialized.
* </p>
*
* @since 3.1
*/
private static final long serialVersionUID = 3257281452776370224L;
/**
* Creates a new bad location exception.
*/
public BadLocationException() {
super();
}
/**
* Creates a new bad location exception.
*
* @param message the exception message
*/
public BadLocationException(String message) {
super(message);
}
}
@@ -1,191 +0,0 @@
/**
* Copyright (c) 2018 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
* http://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
*/
package org.eclipse.lemminx.commons;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.eclipse.lsp4j.CodeAction;
import org.eclipse.lsp4j.CodeActionKind;
import org.eclipse.lsp4j.Command;
import org.eclipse.lsp4j.CreateFile;
import org.eclipse.lsp4j.CreateFileOptions;
import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.ResourceOperation;
import org.eclipse.lsp4j.TextDocumentEdit;
import org.eclipse.lsp4j.TextDocumentItem;
import org.eclipse.lsp4j.TextEdit;
import org.eclipse.lsp4j.VersionedTextDocumentIdentifier;
import org.eclipse.lsp4j.WorkspaceEdit;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
/**
* Factory for simple {@link CodeAction}
*
*/
public class CodeActionFactory {
/**
* Create a CodeAction to remove the content from the given range.
*
* @param title
* @param range
* @param document
* @param diagnostic
* @return
*/
public static CodeAction remove(String title, Range range, TextDocumentItem document, Diagnostic diagnostic) {
return replace(title, range, "", document, diagnostic);
}
/**
* Create a CodeAction to insert a new content at the end of the given range.
*
* @param title
* @param range
* @param insertText
* @param document
* @param diagnostic
*
* @return the CodeAction to insert a new content at the end of the given range.
*/
public static CodeAction insert(String title, Position position, String insertText, TextDocumentItem document,
Diagnostic diagnostic) {
CodeAction insertContentAction = new CodeAction(title);
insertContentAction.setKind(CodeActionKind.QuickFix);
insertContentAction.setDiagnostics(Arrays.asList(diagnostic));
TextDocumentEdit textDocumentEdit = insertEdit(insertText, position, document);
WorkspaceEdit workspaceEdit = new WorkspaceEdit(Collections.singletonList(Either.forLeft(textDocumentEdit)));
insertContentAction.setEdit(workspaceEdit);
return insertContentAction;
}
/**
* Returns the text edit to insert a new content at the end of the given range.
*
* @param insertText text to insert.
* @param position the position.
* @param document the text document.
*
* @return the text edit to insert a new content at the end of the given range.
*/
public static TextDocumentEdit insertEdit(String insertText, Position position, TextDocumentItem document) {
TextEdit edit = insertEdit(insertText, position);
return insertEdits(document, Collections.singletonList(edit));
}
public static TextEdit insertEdit(String insertText, Position position) {
return new TextEdit(new Range(position, position), insertText);
}
public static TextDocumentEdit insertEdits(TextDocumentItem document, List<TextEdit> edits) {
VersionedTextDocumentIdentifier versionedTextDocumentIdentifier = new VersionedTextDocumentIdentifier(
document.getUri(), document.getVersion());
return new TextDocumentEdit(versionedTextDocumentIdentifier, edits);
}
public static CodeAction replace(String title, Range range, String replaceText, TextDocumentItem document,
Diagnostic diagnostic) {
TextEdit replace = new TextEdit(range, replaceText);
return replace(title, Collections.singletonList(replace), document, diagnostic);
}
public static CodeAction replace(String title, List<TextEdit> replace, TextDocumentItem document,
Diagnostic diagnostic) {
CodeAction insertContentAction = new CodeAction(title);
insertContentAction.setKind(CodeActionKind.QuickFix);
insertContentAction.setDiagnostics(Arrays.asList(diagnostic));
VersionedTextDocumentIdentifier versionedTextDocumentIdentifier = new VersionedTextDocumentIdentifier(
document.getUri(), document.getVersion());
TextDocumentEdit textDocumentEdit = new TextDocumentEdit(versionedTextDocumentIdentifier, replace);
WorkspaceEdit workspaceEdit = new WorkspaceEdit(Collections.singletonList(Either.forLeft(textDocumentEdit)));
insertContentAction.setEdit(workspaceEdit);
return insertContentAction;
}
public static CodeAction replaceAt(String title, String replaceText, TextDocumentItem document,
Diagnostic diagnostic, Collection<Range> ranges) {
CodeAction insertContentAction = new CodeAction(title);
insertContentAction.setKind(CodeActionKind.QuickFix);
insertContentAction.setDiagnostics(Arrays.asList(diagnostic));
VersionedTextDocumentIdentifier versionedTextDocumentIdentifier = new VersionedTextDocumentIdentifier(
document.getUri(), document.getVersion());
List<TextEdit> edits = new ArrayList<>();
for (Range range : ranges) {
TextEdit edit = new TextEdit(range, replaceText);
edits.add(edit);
}
TextDocumentEdit textDocumentEdit = new TextDocumentEdit(versionedTextDocumentIdentifier, edits);
WorkspaceEdit workspaceEdit = new WorkspaceEdit(Collections.singletonList(Either.forLeft(textDocumentEdit)));
insertContentAction.setEdit(workspaceEdit);
return insertContentAction;
}
/**
* Makes a CodeAction to create a file and add content to the file.
*
* @param title The displayed name of the CodeAction
* @param docURI The file to create
* @param content The text to put into the newly created document.
* @param diagnostic The diagnostic that this CodeAction will fix
*/
public static CodeAction createFile(String title, String docURI, String content, Diagnostic diagnostic) {
List<Either<TextDocumentEdit, ResourceOperation>> actionsToTake = new ArrayList<>(2);
// 1. create an empty file
actionsToTake.add(Either.forRight(new CreateFile(docURI, new CreateFileOptions(false, true))));
// 2. update the created file with the given content
VersionedTextDocumentIdentifier identifier = new VersionedTextDocumentIdentifier(docURI, 0);
TextEdit te = new TextEdit(new Range(new Position(0, 0), new Position(0, 0)), content);
actionsToTake.add(Either.forLeft(new TextDocumentEdit(identifier, Collections.singletonList(te))));
WorkspaceEdit createAndAddContentEdit = new WorkspaceEdit(actionsToTake);
CodeAction codeAction = new CodeAction(title);
codeAction.setEdit(createAndAddContentEdit);
codeAction.setDiagnostics(Collections.singletonList(diagnostic));
codeAction.setKind(CodeActionKind.QuickFix);
return codeAction;
}
/**
* Makes a CodeAction to call a command from the available server commands.
*
* @param title The displayed name of the CodeAction
* @param commandId The id of the given command to add as CodeAction
* @param commandParams The document URI of the document the command is called on
* @param diagnostic The diagnostic that this CodeAction will fix
*/
public static CodeAction createCommand(String title, String commandId, List<Object> commandParams, Diagnostic diagnostic) {
CodeAction codeAction = new CodeAction(title);
Command command = new Command(title, commandId, commandParams);
codeAction.setCommand(command);
codeAction.setDiagnostics(Collections.singletonList(diagnostic));
codeAction.setKind(CodeActionKind.QuickFix);
return codeAction;
}
}
@@ -1,150 +0,0 @@
/*******************************************************************************
* Copyright (c) 2000, 2009 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx.commons;
//package org.eclipse.jface.text;
import org.eclipse.lsp4j.Position;
/**
* A line tracker maps character positions to line numbers and vice versa.
* Initially the line tracker is informed about its underlying text in order to
* initialize the mapping information. After that, the line tracker is informed
* about all changes of the underlying text allowing for incremental updates of
* the mapping information. It is the client's responsibility to actively inform
* the line tacker about text changes. For example, when using a line tracker in
* combination with a document the document controls the line tracker.
* <p>
* In order to provide backward compatibility for clients of <code>ILineTracker</code>, extension
* interfaces are used to provide a means of evolution. The following extension interfaces
* exist:
* <ul>
* <li> {@link org.eclipse.jface.text.ILineTrackerExtension} since version 3.1 introducing the concept
* of rewrite sessions.</li>
* </ul>
* <p>
* Clients may implement this interface or use the standard implementation
* </p>
* {@link org.eclipse.jface.text.DefaultLineTracker}or
* {@link org.eclipse.jface.text.ConfigurableLineTracker}.
*/
public interface ILineTracker {
/**
* Returns the line delimiter of the specified line. Returns <code>null</code> if the
* line is not closed with a line delimiter.
*
* @param line the line whose line delimiter is queried
* @return the line's delimiter or <code>null</code> if line does not have a delimiter
* @exception BadLocationException if the line number is invalid in this tracker's line structure
*/
String getLineDelimiter(int line) throws BadLocationException;
/**
* Computes the number of lines in the given text.
*
* @param text the text whose number of lines should be computed
* @return the number of lines in the given text
*/
int computeNumberOfLines(String text);
/**
* Returns the number of lines.
* <p>
* Note that a document always has at least one line.
* </p>
*
* @return the number of lines in this tracker's line structure
*/
int getNumberOfLines();
/**
* Returns the number of lines which are occupied by a given text range.
*
* @param offset the offset of the specified text range
* @param length the length of the specified text range
* @return the number of lines occupied by the specified range
* @exception BadLocationException if specified range is unknown to this tracker
*/
int getNumberOfLines(int offset, int length) throws BadLocationException;
/**
* Returns the position of the first character of the specified line.
*
* @param line the line of interest
* @return offset of the first character of the line
* @exception BadLocationException if the line is unknown to this tracker
*/
int getLineOffset(int line) throws BadLocationException;
/**
* Returns length of the specified line including the line's delimiter.
*
* @param line the line of interest
* @return the length of the line
* @exception BadLocationException if line is unknown to this tracker
*/
int getLineLength(int line) throws BadLocationException;
/**
* Returns the line number the character at the given offset belongs to.
*
* @param offset the offset whose line number to be determined
* @return the number of the line the offset is on
* @exception BadLocationException if the offset is invalid in this tracker
*/
int getLineNumberOfOffset(int offset) throws BadLocationException;
/**
* Returns a line description of the line at the given offset.
* The description contains the start offset and the length of the line
* excluding the line's delimiter.
*
* @param offset the offset whose line should be described
* @return a region describing the line
* @exception BadLocationException if offset is invalid in this tracker
*/
Line getLineInformationOfOffset(int offset) throws BadLocationException;
/**
* Returns a line description of the given line. The description
* contains the start offset and the length of the line excluding the line's
* delimiter.
*
* @param line the line that should be described
* @return a region describing the line
* @exception BadLocationException if line is unknown to this tracker
*/
Line getLineInformation(int line) throws BadLocationException;
/**
* Informs the line tracker about the specified change in the tracked text.
*
* @param offset the offset of the replaced text
* @param length the length of the replaced text
* @param text the substitution text
* @exception BadLocationException if specified range is unknown to this tracker
*/
void replace(int offset, int length, String text) throws BadLocationException;
/**
* Sets the tracked text to the specified text.
*
* @param text the new tracked text
*/
void set(String text);
Position getPositionAt(int position) throws BadLocationException;
int getOffsetAt(Position position) throws BadLocationException;
}
@@ -1,71 +0,0 @@
/*******************************************************************************
* Copyright (c) 2000, 2006 IBM Corporation 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:
* IBM Corporation - initial API and implementation
* This class is a copy/paste of org.eclipse.jface.text.Line
*******************************************************************************/
package org.eclipse.lemminx.commons;
/**
* Describes a line as a particular number of characters beginning at
* a particular offset, consisting of a particular number of characters,
* and being closed with a particular line delimiter.
*/
final class Line /*implements IRegion*/ {
/** The offset of the line */
public int offset;
/** The length of the line */
public int length;
/** The delimiter of this line */
public final String delimiter;
/**
* Creates a new Line.
*
* @param offset the offset of the line
* @param end the last including character offset of the line
* @param delimiter the line's delimiter
*/
public Line(int offset, int end, String delimiter) {
this.offset= offset;
this.length= (end - offset) +1;
this.delimiter= delimiter;
}
/**
* Creates a new Line.
*
* @param offset the offset of the line
* @param length the length of the line
*/
public Line(int offset, int length) {
this.offset= offset;
this.length= length;
this.delimiter= null;
}
//@Override
public int getOffset() {
return offset;
}
//@Override
public int getLength() {
return length;
}
@Override
public String toString() {
return "Line [offset: " + offset + ", length: " + length + ", delimiter: '" + delimiter + "']"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
}
}
@@ -1,452 +0,0 @@
/*******************************************************************************
* Copyright (c) 2000, 2008 IBM Corporation 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:
* IBM Corporation - initial API and implementation
* This class is a copy/paste of org.eclipse.jface.text.ListLineTracker
*******************************************************************************/
package org.eclipse.lemminx.commons;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.lsp4j.Position;
/**
* Abstract, read-only implementation of <code>ILineTracker</code>. It lets the
* definition of line delimiters to subclasses. Assuming that '\n' is the only
* line delimiter, this abstract implementation defines the following line
* scheme:
* <ul>
* <li>"" -> [0,0]
* <li>"a" -> [0,1]
* <li>"\n" -> [0,1], [1,0]
* <li>"a\n" -> [0,2], [2,0]
* <li>"a\nb" -> [0,2], [2,1]
* <li>"a\nbc\n" -> [0,2], [2,3], [5,0]
* </ul>
* This class must be subclassed.
*
* @since 3.2
*/
class ListLineTracker implements ILineTracker {
/** The predefined delimiters of this tracker */
public final static String[] DELIMITERS = { "\r", "\n", "\r\n" }; //$NON-NLS-3$ //$NON-NLS-1$ //$NON-NLS-2$
/** A predefined delimiter information which is always reused as return value */
private DelimiterInfo fDelimiterInfo = new DelimiterInfo();
/** The line information */
private final List<Line> fLines = new ArrayList<>();
/** The length of the tracked text */
private int fTextLength;
/**
* Combines the information of the occurrence of a line delimiter.
* <code>delimiterIndex</code> is the index where a line delimiter starts,
* whereas <code>delimiterLength</code>, indicates the length of the delimiter.
*/
protected static class DelimiterInfo {
public int delimiterIndex;
public int delimiterLength;
public String delimiter;
}
/**
* Creates a new line tracker.
*/
protected ListLineTracker() {
}
/**
* Binary search for the line at a given offset.
*
* @param offset the offset whose line should be found
* @return the line of the offset
*/
private int findLine(int offset) {
if (fLines.size() == 0) {
return -1;
}
int left = 0;
int right = fLines.size();
while (left < right - 1) {
int mid = (right + left) >> 1;
Line line = fLines.get(mid);
if (line.offset <= offset) {
left = mid;
} else {
right = mid;
}
}
return (fLines.get(left).offset > offset) ? left - 1 : left;
}
@Override
public final Position getPositionAt(int offset) throws BadLocationException {
int lineNumber = getLineNumberOfOffset(offset);
int lines = fLines.size();
int character = 0;
if (lines > 0) {
if (lineNumber == lines) {
Line l = fLines.get(lineNumber - 1);
character = offset - l.offset - l.length;
} else {
Line l = fLines.get(lineNumber);
character = offset - l.offset;
}
}
return new Position(lineNumber, character);
}
/**
* Returns the number of lines covered by the specified text range.
*
* @param startLine the line where the text range starts
* @param offset the start offset of the text range
* @param length the length of the text range
* @return the number of lines covered by this text range
* @exception BadLocationException if range is undefined in this tracker
*/
private int getNumberOfLines(int startLine, int offset, int length) throws BadLocationException {
if (length == 0) {
return 1;
}
int target = offset + length;
Line l = fLines.get(startLine);
if (l.delimiter == null) {
return 1;
}
if (l.offset + l.length > target) {
return 1;
}
if (l.offset + l.length == target) {
return 2;
}
return getLineNumberOfOffset(target) - startLine + 1;
}
@Override
public final int getLineLength(int line) throws BadLocationException {
int lines = fLines.size();
if (line < 0 || line > lines) {
throw new BadLocationException();
}
if (lines == 0 || lines == line) {
return 0;
}
Line l = fLines.get(line);
return l.length;
}
@Override
public final int getLineNumberOfOffset(int position) throws BadLocationException {
if (position < 0) {
throw new BadLocationException("Negative offset : " + position); //$NON-NLS-1$
} else if (position > fTextLength) {
throw new BadLocationException("Offset > length: " + position + " > " + fTextLength); //$NON-NLS-1$//$NON-NLS-2$
}
if (position == fTextLength) {
int lastLine = fLines.size() - 1;
if (lastLine < 0) {
return 0;
}
Line l = fLines.get(lastLine);
return (l.delimiter != null ? lastLine + 1 : lastLine);
}
return findLine(position);
}
@Override
public int getOffsetAt(Position position) throws BadLocationException {
int line = position.getLine();
int lines = fLines.size();
if (line < 0 || line > lines) {
throw new BadLocationException("The line value, {" + line + "}, is out of bounds.");
}
int lineOffset = -1;
int lineLength = -1;
if (lines == 0) {
lineOffset = 0;
lineLength = 0;
} else {
if (line == lines) {
Line l = fLines.get(line - 1);
lineOffset = l.offset + l.length;
lineLength = 0;
} else {
Line l = fLines.get(line);
lineOffset = l.offset;
lineLength = l.delimiter != null ? l.length - l.delimiter.length() : l.length;
}
}
int character = position.getCharacter();
int offset = lineOffset + character;
int endLineOffset = lineOffset + lineLength;
if (offset > endLineOffset) {
throw new BadLocationException(
"The character value, {" + character + "} of the line" + line + "}, is out of bounds.");
}
return offset;
}
@Override
public final Line getLineInformationOfOffset(int position) throws BadLocationException {
if (position > fTextLength) {
throw new BadLocationException("Offset > length: " + position + " > " + fTextLength); //$NON-NLS-1$//$NON-NLS-2$
}
if (position == fTextLength) {
int size = fLines.size();
if (size == 0) {
return new Line(0, 0);
}
Line l = fLines.get(size - 1);
return (l.delimiter != null ? new Line(fTextLength, 0) : new Line(fTextLength - l.length, l.length));
}
return getLineInformation(findLine(position));
}
@Override
public final Line getLineInformation(int line) throws BadLocationException {
int lines = fLines.size();
if (line < 0 || line > lines) {
throw new BadLocationException("The line value, {" + line + "}, is out of bounds.");
}
if (lines == 0) {
return new Line(0, 0);
}
if (line == lines) {
Line l = fLines.get(line - 1);
return new Line(l.offset + l.length, 0);
}
Line l = fLines.get(line);
return (l.delimiter != null ? new Line(l.offset, l.length - l.delimiter.length()) : l);
}
@Override
public final int getLineOffset(int line) throws BadLocationException {
int lines = fLines.size();
if (line < 0 || line > lines) {
throw new BadLocationException("The line value, {" + line + "}, is out of bounds.");
}
if (lines == 0) {
return 0;
}
if (line == lines) {
Line l = fLines.get(line - 1);
if (l.delimiter != null) {
return l.offset + l.length;
}
throw new BadLocationException();
}
Line l = fLines.get(line);
return l.offset;
}
@Override
public final int getNumberOfLines() {
int lines = fLines.size();
if (lines == 0) {
return 1;
}
Line l = fLines.get(lines - 1);
return (l.delimiter != null ? lines + 1 : lines);
}
@Override
public final int getNumberOfLines(int position, int length) throws BadLocationException {
if (position < 0 || position + length > fTextLength) {
throw new BadLocationException();
}
if (length == 0) {
return 1;
}
return getNumberOfLines(getLineNumberOfOffset(position), position, length);
}
@Override
public final int computeNumberOfLines(String text) {
int count = 0;
int start = 0;
DelimiterInfo delimiterInfo = nextDelimiterInfo(text, start);
while (delimiterInfo != null && delimiterInfo.delimiterIndex > -1) {
++count;
start = delimiterInfo.delimiterIndex + delimiterInfo.delimiterLength;
delimiterInfo = nextDelimiterInfo(text, start);
}
return count;
}
public final String getLineDelimiter(int line) throws BadLocationException {
int lines = fLines.size();
if (line < 0 || line > lines) {
throw new BadLocationException("The line value, {" + line + "}, is out of bounds.");
}
if (lines == 0) {
return null;
}
if (line == lines) {
return null;
}
Line l = fLines.get(line);
return l.delimiter;
}
/**
* Returns the information about the first delimiter found in the given text
* starting at the given offset.
*
* @param text the text to be searched
* @param offset the offset in the given text
* @return the information of the first found delimiter or <code>null</code>
*/
protected DelimiterInfo nextDelimiterInfo(String text, int offset) {
char ch;
int length = text.length();
for (int i = offset; i < length; i++) {
ch = text.charAt(i);
if (ch == '\r') {
if (i + 1 < length) {
if (text.charAt(i + 1) == '\n') {
fDelimiterInfo.delimiter = DELIMITERS[2];
fDelimiterInfo.delimiterIndex = i;
fDelimiterInfo.delimiterLength = 2;
return fDelimiterInfo;
}
}
fDelimiterInfo.delimiter = DELIMITERS[0];
fDelimiterInfo.delimiterIndex = i;
fDelimiterInfo.delimiterLength = 1;
return fDelimiterInfo;
} else if (ch == '\n') {
fDelimiterInfo.delimiter = DELIMITERS[1];
fDelimiterInfo.delimiterIndex = i;
fDelimiterInfo.delimiterLength = 1;
return fDelimiterInfo;
}
}
return null;
}
/**
* Creates the line structure for the given text. Newly created lines are
* inserted into the line structure starting at the given position. Returns the
* number of newly created lines.
*
* @param text the text for which to create a line structure
* @param insertPosition the position at which the newly created lines are
* inserted into the tracker's line structure
* @param offset the offset of all newly created lines
* @return the number of newly created lines
*/
private int createLines(String text, int insertPosition, int offset) {
int count = 0;
int start = 0;
DelimiterInfo delimiterInfo = nextDelimiterInfo(text, 0);
while (delimiterInfo != null && delimiterInfo.delimiterIndex > -1) {
int index = delimiterInfo.delimiterIndex + (delimiterInfo.delimiterLength - 1);
if (insertPosition + count >= fLines.size()) {
fLines.add(new Line(offset + start, offset + index, delimiterInfo.delimiter));
} else {
fLines.add(insertPosition + count, new Line(offset + start, offset + index, delimiterInfo.delimiter));
}
++count;
start = index + 1;
delimiterInfo = nextDelimiterInfo(text, start);
}
if (start < text.length()) {
if (insertPosition + count < fLines.size()) {
// there is a line below the current
Line l = fLines.get(insertPosition + count);
int delta = text.length() - start;
l.offset -= delta;
l.length += delta;
} else {
fLines.add(new Line(offset + start, offset + text.length() - 1, null));
++count;
}
}
return count;
}
@Override
public final void replace(int position, int length, String text) throws BadLocationException {
throw new UnsupportedOperationException();
}
@Override
public final void set(String text) {
fLines.clear();
if (text != null) {
fTextLength = text.length();
createLines(text, 0, 0);
}
}
/**
* Returns the internal data structure, a {@link List} of {@link Line}s. Used
* only by {@link TreeLineTracker#TreeLineTracker(ListLineTracker)}.
*
* @return the internal list of lines.
*/
final List<Line> getLines() {
return fLines;
}
}
@@ -1,104 +0,0 @@
/*******************************************************************************
* Copyright (c) 2019 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
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx.commons;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiFunction;
import java.util.logging.Logger;
import org.eclipse.lsp4j.TextDocumentItem;
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
import org.eclipse.lsp4j.jsonrpc.CompletableFutures;
/**
* A {@link TextDocument} which is associate to a model loaded in async.
*
* @author Angelo ZERR
*
* @param <T> the model type (ex : DOM Document)
*/
public class ModelTextDocument<T> extends TextDocument {
private static final Logger LOGGER = Logger.getLogger(ModelTextDocument.class.getName());
private final BiFunction<TextDocument, CancelChecker, T> parse;
private CompletableFuture<T> model;
public ModelTextDocument(TextDocumentItem document, BiFunction<TextDocument, CancelChecker, T> parse) {
super(document);
this.parse = parse;
}
public ModelTextDocument(String text, String uri, BiFunction<TextDocument, CancelChecker, T> parse) {
super(text, uri);
this.parse = parse;
}
/**
* Returns the completable future which loads the model. The process of parse od
* the model is stopped as soon as possible when text content changed.
*
* @return the completable future which loads the model.
*/
public CompletableFuture<T> getModel() {
if (model == null) {
int version = super.getVersion();
model = CompletableFutures.computeAsync((requestCancelChecker) -> {
long start = System.currentTimeMillis();
try {
LOGGER.fine("Start parsing of model with version '" + version);
// Stop of parse process can be done when completable future is canceled or when
// version of document changes
MultiCancelChecker cancelChecker = new MultiCancelChecker(requestCancelChecker,
new TextDocumentVersionChecker(this, version));
// parse the model
return parse.apply(this, cancelChecker);
} catch (CancellationException e) {
LOGGER.fine("Stop parsing parsing of model with version '" + version + "' in "
+ (System.currentTimeMillis() - start) + "ms");
throw e;
} finally {
LOGGER.fine("End parse of model with version '" + version + "' in "
+ (System.currentTimeMillis() - start) + "ms");
}
});
}
return model;
}
@Override
public void setText(String text) {
super.setText(text);
// text changed, cancel the completable future which load the model
cancelModel();
}
@Override
public void setVersion(int version) {
super.setVersion(version);
// version changed, cancel the completable future which load the model
cancelModel();
}
/**
* Cancel the completable future which loads the model.
*/
private void cancelModel() {
if (model != null) {
model.cancel(true);
model = null;
}
}
}
@@ -1,40 +0,0 @@
/*******************************************************************************
* Copyright (c) 2019 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
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx.commons;
import java.util.function.BiFunction;
import org.eclipse.lsp4j.TextDocumentItem;
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
/**
* The cache of {@link TextDocument} linked to a model.
*
* @author Angelo ZERR
*
* @param <T> the model type (ex : DOM Document)
*/
public class ModelTextDocuments<T> extends TextDocuments<ModelTextDocument<T>> {
private final BiFunction<TextDocument, CancelChecker, T> parse;
public ModelTextDocuments(BiFunction<TextDocument, CancelChecker, T> parse) {
this.parse = parse;
}
@Override
public ModelTextDocument<T> createDocument(TextDocumentItem document) {
ModelTextDocument<T> doc = new ModelTextDocument<T>(document, parse);
doc.setIncremental(isIncremental());
return doc;
}
}
@@ -1,36 +0,0 @@
/*******************************************************************************
* Copyright (c) 2019 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
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx.commons;
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
/**
* Multiple cancel checker.
*
* @author Angelo ZERR
*
*/
public class MultiCancelChecker implements CancelChecker {
private CancelChecker[] checkers;
public MultiCancelChecker(CancelChecker... checkers) {
this.checkers = checkers;
}
@Override
public void checkCanceled() {
for (CancelChecker cancelChecker : checkers) {
cancelChecker.checkCanceled();
}
}
}
@@ -1,146 +0,0 @@
/*******************************************************************************
* Copyright (c) 2017 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.commons;
import static org.eclipse.lemminx.utils.platform.Platform.isWindows;
import java.io.IOException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.google.common.io.Closeables;
import org.eclipse.lsp4j.jsonrpc.MessageConsumer;
import org.eclipse.lsp4j.services.LanguageServer;
/**
* Watches the parent process PID and invokes exit if it is no longer available.
* This implementation waits for periods of inactivity to start querying the
* PIDs.
*/
public final class ParentProcessWatcher implements Runnable, Function<MessageConsumer, MessageConsumer> {
private static final Logger LOGGER = Logger.getLogger(ParentProcessWatcher.class.getName());
private static final boolean isJava1x = System.getProperty("java.version").startsWith("1.");
/**
* Exit code returned when XML Language Server is forced to exit.
*/
private static final int FORCED_EXIT_CODE = 1;
private static final long INACTIVITY_DELAY_SECS = 30 * 1000;
private static final int POLL_DELAY_SECS = 10;
private volatile long lastActivityTime;
private final ProcessLanguageServer server;
private ScheduledFuture<?> task;
private ScheduledExecutorService service;
public interface ProcessLanguageServer extends LanguageServer {
long getParentProcessId();
void exit(int exitCode);
}
public ParentProcessWatcher(ProcessLanguageServer server) {
this.server = server;
service = Executors.newScheduledThreadPool(1);
task = service.scheduleWithFixedDelay(this, POLL_DELAY_SECS, POLL_DELAY_SECS, TimeUnit.SECONDS);
}
@Override
public void run() {
if (!parentProcessStillRunning()) {
LOGGER.warning("Parent process stopped running, forcing server exit");
task.cancel(true);
server.exit(FORCED_EXIT_CODE);
}
}
/**
* Checks whether the parent process is still running. If not, then we assume it
* has crashed, and we have to terminate the Java Language Server.
*
* @return true if the parent process is still running
*/
private boolean parentProcessStillRunning() {
// Wait until parent process id is available
final long pid = server.getParentProcessId();
if (pid == 0 || lastActivityTime > (System.currentTimeMillis() - INACTIVITY_DELAY_SECS)) {
return true;
}
String command;
if (isWindows) {
command = "cmd /c \"tasklist /FI \"PID eq " + pid + "\" | findstr " + pid + "\"";
} else {
command = "kill -0 " + pid;
}
Process process = null;
boolean finished = false;
try {
process = Runtime.getRuntime().exec(command);
finished = process.waitFor(POLL_DELAY_SECS, TimeUnit.SECONDS);
if (!finished) {
process.destroy();
finished = process.waitFor(POLL_DELAY_SECS, TimeUnit.SECONDS); // wait for the process to stop
}
if (isWindows && finished && process.exitValue() > 1) {
// the tasklist command should return 0 (parent process exists) or 1 (parent process doesn't exist)
LOGGER.warning("The tasklist command: '" + command + "' returns " + process.exitValue());
return true;
}
return !finished || process.exitValue() == 0;
} catch (IOException | InterruptedException e) {
LOGGER.log(Level.WARNING, e.getMessage(), e);
return true;
} finally {
if (process != null) {
if (!finished) {
process.destroyForcibly();
}
// Terminating or destroying the Process doesn't close the process handle on Windows.
// It is only closed when the Process object is garbage collected (in its finalize() method).
// On Windows, when the Java LS is idle, we need to explicitly request a GC,
// to prevent an accumulation of zombie processes, as finalize() will be called.
if (isWindows) {
// Java >= 9 doesn't close the handle when the process is garbage collected
// We need to close the opened streams
if (!isJava1x) {
Closeables.closeQuietly(process.getInputStream());
Closeables.closeQuietly(process.getErrorStream());
try {
Closeables.close(process.getOutputStream(), false);
} catch (IOException e) {
}
}
System.gc();
}
}
}
}
@Override
public MessageConsumer apply(final MessageConsumer consumer) {
//inject our own consumer to refresh the timestamp
return message -> {
lastActivityTime=System.currentTimeMillis();
consumer.consume(message);
};
}
}
@@ -1,88 +0,0 @@
/**
* Copyright (c) 2018 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
* http://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
*/
package org.eclipse.lemminx.commons;
import java.util.Collection;
/**
* Snippet syntax utilities.
*
* @see https://github.com/Microsoft/language-server-protocol/blob/master/snippetSyntax.md
*/
public class SnippetsBuilder {
private SnippetsBuilder() {
}
public static void tabstops(int index, StringBuilder snippets) {
snippets.append("$");
snippets.append(index);
}
public static String tabstops(int index) {
StringBuilder snippets = new StringBuilder();
snippets.append("$");
snippets.append(index);
return snippets.toString();
}
public static void placeholders(int index, String text, StringBuilder snippets) {
snippets.append("${");
snippets.append(index);
snippets.append(":");
snippets.append(text);
snippets.append("}");
}
/**
* Returns the LSP choices snippets content.
*
* @param index the snippet index.
* @param values the values for the choice.
* @return the LSP choices snippets content.
*
* @see https://github.com/Microsoft/language-server-protocol/blob/master/snippetSyntax.md#choice
*/
public static String choice(int index, Collection<String> values) {
StringBuilder snippets = new StringBuilder();
choice(index, values, snippets);
return snippets.toString();
}
/**
* Add LSP choices snippets in the given snippets content.
*
* @param index the snippet index.
* @param values the values for the choice.
* @return
*
* @see https://github.com/Microsoft/language-server-protocol/blob/master/snippetSyntax.md#choice
*/
public static void choice(int index, Collection<String> values, StringBuilder snippets) {
snippets.append("${");
snippets.append(index);
snippets.append("|");
int i = 0;
for (String value : values) {
if (i > 0) {
snippets.append(",");
}
snippets.append(value);
i++;
}
snippets.append("|");
snippets.append("}");
}
}
@@ -1,193 +0,0 @@
/**
* Copyright (c) 2018 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
* http://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
*/
package org.eclipse.lemminx.commons;
import java.util.List;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.TextDocumentContentChangeEvent;
import org.eclipse.lsp4j.TextDocumentItem;
/**
* Text document extends LSP4j {@link TextDocumentItem} to provide methods to
* retrieve position.
*
*/
public class TextDocument extends TextDocumentItem {
private static final Logger LOGGER = Logger.getLogger(TextDocument.class.getName());
private final Object lock = new Object();
private static String DEFAULT_DELIMTER = System.lineSeparator();
private ILineTracker lineTracker;
private boolean incremental;
public TextDocument(TextDocumentItem document) {
this(document.getText(), document.getUri());
super.setVersion(document.getVersion());
super.setLanguageId(document.getLanguageId());
}
public TextDocument(String text, String uri) {
super.setUri(uri);
super.setText(text);
}
public void setIncremental(boolean incremental) {
this.incremental = incremental;
// reset line tracker
lineTracker = null;
getLineTracker();
}
public boolean isIncremental() {
return incremental;
}
public Position positionAt(int position) throws BadLocationException {
ILineTracker lineTracker = getLineTracker();
return lineTracker.getPositionAt(position);
}
public int offsetAt(Position position) throws BadLocationException {
ILineTracker lineTracker = getLineTracker();
return lineTracker.getOffsetAt(position);
}
public String lineText(int lineNumber) throws BadLocationException {
ILineTracker lineTracker = getLineTracker();
Line line = lineTracker.getLineInformation(lineNumber);
String text = super.getText();
return text.substring(line.offset, line.offset + line.length);
}
public String lineDelimiter(int lineNumber) throws BadLocationException {
ILineTracker lineTracker = getLineTracker();
String lineDelimiter = lineTracker.getLineDelimiter(lineNumber);
if (lineDelimiter == null) {
if (lineTracker.getNumberOfLines() > 0) {
lineDelimiter = lineTracker.getLineInformation(0).delimiter;
}
}
if (lineDelimiter == null) {
lineDelimiter = DEFAULT_DELIMTER;
}
return lineDelimiter;
}
public Range getWordRangeAt(int textOffset, Pattern wordDefinition) {
try {
Position pos = positionAt(textOffset);
ILineTracker lineTracker = getLineTracker();
Line line = lineTracker.getLineInformation(pos.getLine());
String text = super.getText();
String lineText = text.substring(line.offset, textOffset);
int position = lineText.length();
Matcher m = wordDefinition.matcher(lineText);
int currentPosition = 0;
while (currentPosition != position) {
if (m.find()) {
currentPosition = m.end();
if (currentPosition == position) {
return new Range(new Position(pos.getLine(), m.start()), pos);
}
} else {
currentPosition++;
}
m.region(currentPosition, position);
}
return new Range(pos, pos);
} catch (BadLocationException e) {
return null;
}
}
private ILineTracker getLineTracker() {
if (lineTracker == null) {
lineTracker = createLineTracker();
}
return lineTracker;
}
private synchronized ILineTracker createLineTracker() {
if (lineTracker != null) {
return lineTracker;
}
ILineTracker lineTracker = isIncremental() ? new TreeLineTracker(new ListLineTracker()) : new ListLineTracker();
lineTracker.set(super.getText());
return lineTracker;
}
/**
* Update text of the document by using the changes and according the
* incremental support.
*
* @param changes the text document changes.
*/
public void update(List<TextDocumentContentChangeEvent> changes) {
if (changes.size() < 1) {
// no changes, ignore it.
return;
}
if (isIncremental()) {
try {
long start = System.currentTimeMillis();
synchronized (lock) {
// Initialize buffer and line tracker from the current text document
StringBuilder buffer = new StringBuilder(getText());
// Loop for each changes and update the buffer
for (int i = 0; i < changes.size(); i++) {
TextDocumentContentChangeEvent changeEvent = changes.get(i);
Range range = changeEvent.getRange();
int length = 0;
if (range != null) {
length = changeEvent.getRangeLength().intValue();
} else {
// range is optional and if not given, the whole file content is replaced
length = buffer.length();
range = new Range(positionAt(0), positionAt(length));
}
String text = changeEvent.getText();
int startOffset = offsetAt(range.getStart());
buffer.replace(startOffset, startOffset + length, text);
lineTracker.replace(startOffset, length, text);
}
// Update the new text content from the updated buffer
setText(buffer.toString());
}
LOGGER.fine("Text document content updated in " + (System.currentTimeMillis() - start) + "ms");
} catch (BadLocationException e) {
// Should never occur.
}
} else {
// like vscode does, get the last changes
// see
// https://github.com/Microsoft/vscode-languageserver-node/blob/master/server/src/main.ts
TextDocumentContentChangeEvent last = changes.size() > 0 ? changes.get(changes.size() - 1) : null;
if (last != null) {
setText(last.getText());
lineTracker.set(last.getText());
}
}
}
}
@@ -1,45 +0,0 @@
/*******************************************************************************
* Copyright (c) 2019 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
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx.commons;
import java.util.concurrent.CancellationException;
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
/**
* A {@link CancelChecker} implementation to throw a
* {@link CancellationException} when version of {@link TextDocument} changed.
*
* @author Angelo ZERR
*
*/
public class TextDocumentVersionChecker implements CancelChecker {
private final TextDocument textDocument;
private final int version;
public TextDocumentVersionChecker(TextDocument textDocument, int version) {
this.textDocument = textDocument;
this.version = version;
}
@Override
public void checkCanceled() {
if (textDocument.getVersion() != version) {
// the text document version has changed
throw new CancellationException("Text document version '" + version + "' has changed to version '"
+ textDocument.getVersion() + ".");
}
}
}
@@ -1,126 +0,0 @@
/**
* Copyright (c) 2018 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
* http://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
*/
package org.eclipse.lemminx.commons;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.lsp4j.DidChangeTextDocumentParams;
import org.eclipse.lsp4j.DidCloseTextDocumentParams;
import org.eclipse.lsp4j.DidOpenTextDocumentParams;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.eclipse.lsp4j.TextDocumentItem;
/**
* A manager for simple text documents
*/
public class TextDocuments<T extends TextDocument> {
private final Map<String, T> documents;
private boolean incremental = true; //default on
public TextDocuments() {
documents = new HashMap<>();
}
/**
* Set the incremental support.
*
* @param incremental
*/
public void setIncremental(boolean incremental) {
this.incremental = incremental;
synchronized (documents) {
documents.values().forEach(document -> document.setIncremental(incremental));
}
}
/**
* Returns true if text document is managed in incremental mode and false
* otherwise.
*
* @return true if text document is managed in incremental mode and false
* otherwise.
*/
public boolean isIncremental() {
return incremental;
}
/**
* Returns the document for the given URI. Returns undefined if the document is
* not mananged by this instance.
*
* @param uri The text document's URI to retrieve.
* @return the text document or `undefined`.
*/
public T get(String uri) {
synchronized (documents) {
return documents.get(uri);
}
}
public T createDocument(TextDocumentItem document) {
TextDocument doc = new TextDocument(document);
doc.setIncremental(isIncremental());
return (T) doc;
}
public T onDidChangeTextDocument(DidChangeTextDocumentParams params) {
synchronized (documents) {
T document = getDocument(params.getTextDocument());
if (document != null) {
document.setVersion(params.getTextDocument().getVersion());
document.update(params.getContentChanges());
return document;
}
}
return null;
}
public T onDidOpenTextDocument(DidOpenTextDocumentParams params) {
TextDocumentItem item = params.getTextDocument();
synchronized (documents) {
T document = createDocument(item);
documents.put(document.getUri(), document);
return document;
}
}
public T onDidCloseTextDocument(DidCloseTextDocumentParams params) {
synchronized (documents) {
T document = getDocument(params.getTextDocument());
if (document != null) {
documents.remove(params.getTextDocument().getUri());
}
return document;
}
}
private T getDocument(TextDocumentIdentifier identifier) {
return documents.get(identifier.getUri());
}
/**
* Returns the all opened documents.
*
* @return the all opened documents.
*/
public Collection<T> all() {
synchronized (documents) {
return documents.values();
}
}
}
File diff suppressed because it is too large Load Diff
@@ -1,33 +0,0 @@
/*******************************************************************************
* Copyright (c) 2020 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
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx.commons.snippets;
import java.util.Map;
/**
* Snippet context used to filter the snippet.
*
* @author Angelo ZERR
*
* @param <T> the value type waited by the snippet context.
*/
public interface ISnippetContext<T> {
/**
* Return true if the given value match the snippet context and false otherwise.
*
* @param value the value to check.
* @return true if the given value match the snippet context and false
* otherwise.
*/
boolean isMatch(T value, Map<String, String> model);
}
@@ -1,38 +0,0 @@
/*******************************************************************************
* Copyright (c) 2020 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
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx.commons.snippets;
/**
* Loader used to load snippets in a given registry for a language id.
*
* @author Angelo ZERR
*
*/
public interface ISnippetRegistryLoader {
/**
* Register snippets in the given snippet registry.
*
* @param registry
* @throws Exception
*/
void load(SnippetRegistry registry) throws Exception;
/**
* Returns the language id and null otherwise.
*
* @return the language id and null otherwise.
*/
default String getLanguageId() {
return null;
}
}
@@ -1,33 +0,0 @@
/*******************************************************************************
* Copyright (c) 2020 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
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx.commons.snippets;
import org.eclipse.lsp4j.Position;
/**
* Suffix position provider API.
*
* @author Angelo ZERR
*
*/
public interface ISuffixPositionProvider {
/**
* Returns the suffix position provider of the given <code>sufix</code> and null
* otherwise.
*
* @param suffix
* @return the suffix position provider of the given <code>sufix</code> and null
* otherwise.
*/
Position findSuffixPosition(String suffix);
}
@@ -1,118 +0,0 @@
/*******************************************************************************
* Copyright (c) 2020 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
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx.commons.snippets;
import java.util.List;
import java.util.Map;
import java.util.function.BiPredicate;
/**
* Snippet description (like vscode snippet).
*
* @author Angelo ZERR
*
*/
public class Snippet {
private String label;
private List<String> prefixes;
private String suffix;
private List<String> body;
private String description;
private String scope;
private String sortText;
private ISnippetContext<?> context;
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public List<String> getPrefixes() {
return prefixes;
}
public void setPrefixes(List<String> prefixes) {
this.prefixes = prefixes;
}
public String getSuffix() {
return suffix;
}
public void setSuffix(String suffix) {
this.suffix = suffix;
}
public List<String> getBody() {
return body;
}
public void setBody(List<String> body) {
this.body = body;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getScope() {
return scope;
}
public void setScope(String scope) {
this.scope = scope;
}
public String getSortText() {
return sortText;
}
public void setSortText(String sortText) {
this.sortText = sortText;
}
public ISnippetContext<?> getContext() {
return context;
}
public void setContext(ISnippetContext<?> context) {
this.context = context;
}
public boolean hasContext() {
return getContext() != null;
}
public boolean match(BiPredicate<ISnippetContext<?>, Map<String, String>> contextFilter,
Map<String, String> model) {
if (!hasContext()) {
return true;
}
return contextFilter.test(getContext(), model);
}
}
@@ -1,146 +0,0 @@
/*******************************************************************************
* Copyright (c) 2020 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
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx.commons.snippets;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.lemminx.utils.StringUtils;
import com.google.gson.JsonArray;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.TypeAdapter;
/**
* GSON deserializer to build Snippet from vscode JSON snippet.
*
* @author Angelo ZERR
*
*/
class SnippetDeserializer implements JsonDeserializer<Snippet> {
private static final String PREFIX_ELT = "prefix";
private static final String SUFFIX_ELT = "suffix";
private static final String DESCRIPTION_ELT = "description";
private static final String LABEL_ELT = "label";
private static final String SCOPE_ELT = "scope";
private static final String SORTTEXT_ELT = "sortText";
private static final String BODY_ELT = "body";
private static final String CONTEXT_ELT = "context";
private final TypeAdapter<? extends ISnippetContext<?>> contextDeserializer;
public SnippetDeserializer(TypeAdapter<? extends ISnippetContext<?>> contextDeserializer) {
this.contextDeserializer = contextDeserializer;
}
@Override
public Snippet deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
Snippet snippet = new Snippet();
JsonObject snippetObj = json.getAsJsonObject();
// prefix
List<String> prefixes = new ArrayList<>();
JsonElement prefixElt = snippetObj.get(PREFIX_ELT);
if (prefixElt != null) {
if (prefixElt.isJsonArray()) {
JsonArray prefixArray = (JsonArray) prefixElt;
prefixArray.forEach(elt -> {
prefixes.add(elt.getAsString());
});
} else if (prefixElt.isJsonPrimitive()) {
prefixes.add(prefixElt.getAsString());
}
}
snippet.setPrefixes(prefixes);
// by default label is the first prefix
if (!prefixes.isEmpty()) {
snippet.setLabel(prefixes.get(0));
}
// suffix
JsonElement suffixElt = snippetObj.get(SUFFIX_ELT);
if (suffixElt != null) {
String suffix = suffixElt.getAsString();
snippet.setSuffix(suffix);
}
// body
List<String> body = new ArrayList<>();
JsonElement bodyElt = snippetObj.get(BODY_ELT);
if (bodyElt != null) {
if (bodyElt.isJsonArray()) {
JsonArray bodyArray = (JsonArray) bodyElt;
bodyArray.forEach(elt -> {
body.add(elt.getAsString());
});
} else if (bodyElt.isJsonPrimitive()) {
body.add(bodyElt.getAsString());
}
}
snippet.setBody(body);
// description
JsonElement descriptionElt = snippetObj.get(DESCRIPTION_ELT);
if (descriptionElt != null) {
String description = descriptionElt.getAsString();
snippet.setDescription(description);
}
// label
JsonElement labelElt = snippetObj.get(LABEL_ELT);
if (labelElt != null) {
String label = labelElt.getAsString();
if (label.contains("$")) {
if (!StringUtils.isEmpty(snippet.getDescription())) {
label = label.replace("$description", snippet.getDescription());
}
if (!snippet.getPrefixes().isEmpty()) {
label = label.replace("$prefix", snippet.getPrefixes().get(0));
}
}
snippet.setLabel(label);
}
// scope
JsonElement scopeElt = snippetObj.get(SCOPE_ELT);
if (scopeElt != null) {
String scope = scopeElt.getAsString();
snippet.setScope(scope);
}
// sortText
JsonElement sortTextElt = snippetObj.get(SORTTEXT_ELT);
if (sortTextElt != null) {
String sortText = sortTextElt.getAsString();
snippet.setSortText(sortText);
}
// context
if (contextDeserializer != null) {
JsonElement contextElt = snippetObj.get(CONTEXT_ELT);
if (contextElt != null) {
ISnippetContext<?> snippetContext = contextDeserializer.fromJsonTree(contextElt);
snippet.setContext(snippetContext);
}
}
return snippet;
}
}
@@ -1,397 +0,0 @@
/*******************************************************************************
* Copyright (c) 2020 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
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx.commons.snippets;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.ServiceLoader;
import java.util.function.BiPredicate;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import org.eclipse.lemminx.utils.StringUtils;
import org.eclipse.lsp4j.CompletionItem;
import org.eclipse.lsp4j.CompletionItemKind;
import org.eclipse.lsp4j.InsertTextFormat;
import org.eclipse.lsp4j.MarkupContent;
import org.eclipse.lsp4j.MarkupKind;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.TextEdit;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonIOException;
import com.google.gson.JsonSyntaxException;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
/**
* A registry for snippets which uses the same format than vscode snippet.
*
* @author Angelo ZERR
*
*/
public class SnippetRegistry {
private static final Logger LOGGER = Logger.getLogger(SnippetRegistry.class.getName());
private final List<Snippet> snippets;
public SnippetRegistry() {
this(null, true);
}
/**
* Snippet registry for a given language id.
*
* @param languageId the language id and null otherwise.
* @param loadDefault true if default snippets from SPI must be loaded and false
* otherwise.
*/
public SnippetRegistry(String languageId, boolean loadDefault) {
snippets = new ArrayList<>();
// Load snippets from SPI
if (loadDefault) {
ServiceLoader<ISnippetRegistryLoader> loaders = ServiceLoader.load(ISnippetRegistryLoader.class);
loaders.forEach(loader -> {
if (Objects.equals(languageId, loader.getLanguageId())) {
try {
loader.load(this);
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "Error while consumming snippet loader " + loader.getClass().getName(),
e);
}
}
});
}
}
/**
* Register the given snippet.
*
* @param snippet the snippet to register.
*/
public void registerSnippet(Snippet snippet) {
snippets.add(snippet);
}
/**
* Register the snippets from the given JSON input stream.
*
* @param in the JSON input stream which declares snippets with vscode snippet
* format.
* @throws IOException
*/
public void registerSnippets(InputStream in) throws IOException {
registerSnippets(in, null, null);
}
/**
* Register the snippets from the given JSON stream with a context.
*
* @param in the JSON input stream which declares snippets with
* vscode snippet format.
* @param contextDeserializer the GSON context deserializer used to create Java
* context.
* @throws IOException
*/
public void registerSnippets(InputStream in, TypeAdapter<? extends ISnippetContext<?>> contextDeserializer)
throws IOException {
registerSnippets(in, null, contextDeserializer);
}
/**
* Register the snippets from the given JSON stream with a context.
*
* @param in the JSON input stream which declares snippets with
* vscode snippet format.
* @param defaultContext the default context.
* @throws IOException
*/
public void registerSnippets(InputStream in, ISnippetContext<?> defaultContext) throws IOException {
registerSnippets(in, defaultContext, null);
}
/**
* Register the snippets from the given JSON stream with a context.
*
* @param in the JSON input stream which declares snippets with
* vscode snippet format.
* @param defaultContext the default context.
* @param contextDeserializer the GSON context deserializer used to create Java
* context.
* @throws IOException
*/
public void registerSnippets(InputStream in, ISnippetContext<?> defaultContext,
TypeAdapter<? extends ISnippetContext<?>> contextDeserializer) throws IOException {
registerSnippets(new InputStreamReader(in, StandardCharsets.UTF_8.name()), defaultContext, contextDeserializer);
}
/**
* Register the snippets from the given JSON reader.
*
* @param in the JSON reader which declares snippets with vscode snippet format.
* @throws IOException
*/
public void registerSnippets(Reader in) throws IOException {
registerSnippets(in, null, null);
}
/**
* Register the snippets from the given JSON reader with a context.
*
* @param in the JSON reader which declares snippets with
* vscode snippet format.
* @param contextDeserializer the GSON context deserializer used to create Java
* context.
* @throws IOException
*/
public void registerSnippets(Reader in, TypeAdapter<? extends ISnippetContext<?>> contextDeserializer)
throws IOException {
registerSnippets(in, null, contextDeserializer);
}
/**
* Register the snippets from the given JSON stream with a context.
*
* @param in the JSON reader which declares snippets with vscode
* snippet format.
* @param defaultContext the default context.
* @throws IOException
*/
public void registerSnippets(Reader in, ISnippetContext<?> defaultContext) throws IOException {
registerSnippets(in, defaultContext, null);
}
/**
* Register the snippets from the given JSON stream with a context.
*
* @param in the JSON reader which declares snippets with
* vscode snippet format.
* @param defaultContext the default context.
* @param contextDeserializer the GSON context deserializer used to create Java
* context.
* @throws IOException
*/
public void registerSnippets(Reader in, ISnippetContext<?> defaultContext,
TypeAdapter<? extends ISnippetContext<?>> contextDeserializer) throws IOException {
JsonReader reader = new JsonReader(in);
reader.beginObject();
while (reader.hasNext()) {
String name = reader.nextName();
Snippet snippet = createSnippet(reader, contextDeserializer);
if (snippet.getDescription() == null) {
snippet.setDescription(name);
}
if (snippet.getContext() == null) {
snippet.setContext(defaultContext);
}
registerSnippet(snippet);
}
reader.endObject();
}
private static Snippet createSnippet(JsonReader reader,
TypeAdapter<? extends ISnippetContext<?>> contextDeserializer) throws JsonIOException, JsonSyntaxException {
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(Snippet.class, new SnippetDeserializer(contextDeserializer));
return builder.create().fromJson(reader, Snippet.class);
}
/**
* Returns all snippets.
*
* @return all snippets.
*/
public List<Snippet> getSnippets() {
return snippets;
}
/**
* Returns the snippet completion items according to the context filter.
*
* @param replaceRange the replace range.
* @param lineDelimiter the line delimiter.
* @param canSupportMarkdown true if markdown is supported to generate
* documentation and false otherwise.
* @param contextFilter the context filter.
* @return the snippet completion items according to the context filter.
*/
public List<CompletionItem> getCompletionItems(Range replaceRange, String lineDelimiter, boolean canSupportMarkdown,
boolean snippetsSupported, BiPredicate<ISnippetContext<?>, Map<String, String>> contextFilter,
ISuffixPositionProvider suffixProvider) {
if (replaceRange == null) {
return Collections.emptyList();
}
Map<String, String> model = new HashMap<>();
return getSnippets().stream().filter(snippet -> {
return snippet.match(contextFilter, model);
}).map(snippet -> {
CompletionItem item = new CompletionItem();
item.setLabel(snippet.getLabel());
String insertText = getInsertText(snippet, model, !snippetsSupported, lineDelimiter);
item.setKind(CompletionItemKind.Snippet);
item.setDocumentation(
Either.forRight(createDocumentation(snippet, model, canSupportMarkdown, lineDelimiter)));
String prefix = snippet.getPrefixes().get(0);
item.setFilterText(prefix);
item.setDetail(snippet.getDescription());
Range range = replaceRange;
if (!StringUtils.isEmpty(snippet.getSuffix()) && suffixProvider != null) {
Position end = suffixProvider.findSuffixPosition(snippet.getSuffix());
if (end != null) {
range = new Range(replaceRange.getStart(), end);
}
}
item.setTextEdit(Either.forLeft(new TextEdit(range, insertText)));
item.setInsertTextFormat(InsertTextFormat.Snippet);
item.setSortText(snippet.getSortText());
return item;
}).collect(Collectors.toList());
}
private static MarkupContent createDocumentation(Snippet snippet, Map<String, String> model,
boolean canSupportMarkdown, String lineDelimiter) {
StringBuilder doc = new StringBuilder();
if (canSupportMarkdown) {
doc.append(System.lineSeparator());
doc.append("```");
String scope = snippet.getScope();
if (scope != null) {
doc.append(scope);
}
doc.append(System.lineSeparator());
}
String insertText = getInsertText(snippet, model, true, lineDelimiter);
doc.append(insertText);
if (canSupportMarkdown) {
doc.append(System.lineSeparator());
doc.append("```");
doc.append(System.lineSeparator());
}
return new MarkupContent(canSupportMarkdown ? MarkupKind.MARKDOWN : MarkupKind.PLAINTEXT, doc.toString());
}
private static String getInsertText(Snippet snippet, Map<String, String> model, boolean replace,
String lineDelimiter) {
StringBuilder text = new StringBuilder();
int i = 0;
List<String> body = snippet.getBody();
if (body != null) {
for (String bodyLine : body) {
if (i > 0) {
text.append(lineDelimiter);
}
bodyLine = merge(bodyLine, model, replace);
text.append(bodyLine);
i++;
}
}
return text.toString();
}
private static String merge(String line, Map<String, String> model, boolean replace) {
return replace(line, 0, model, replace, null);
}
private static String replace(String line, int offset, Map<String, String> model, boolean replace,
StringBuilder newLine) {
int dollarIndex = line.indexOf("$", offset);
if (dollarIndex == -1 || dollarIndex == line.length() - 1) {
if (newLine == null) {
return line;
}
newLine.append(line, offset, line.length());
return newLine.toString();
}
if (newLine == null) {
newLine = new StringBuilder();
}
char next = line.charAt(dollarIndex + 1);
if (Character.isDigit(next)) {
if (replace) {
newLine.append(line, offset, dollarIndex);
}
int lastDigitOffset = dollarIndex + 1;
while (Character.isDigit(line.charAt(lastDigitOffset))) {
lastDigitOffset++;
}
if (!replace) {
newLine.append(line, offset, lastDigitOffset);
}
return replace(line, lastDigitOffset, model, replace, newLine);
} else if (next == '{') {
int startExpr = dollarIndex;
int endExpr = line.indexOf("}", startExpr);
if (endExpr == -1) {
// Should never occur
return line;
}
newLine.append(line, offset, startExpr);
// Parameter
int startParam = startExpr + 2;
int endParam = endExpr;
boolean startsWithNumber = true;
boolean onlyNumber = true;
for (int i = startParam; i < endParam; i++) {
char ch = line.charAt(i);
if (Character.isDigit(ch)) {
startsWithNumber = true;
} else {
onlyNumber = false;
if (ch == ':') {
if (startsWithNumber) {
startParam = i + 1;
}
break;
} else if (ch == '|') {
if (startsWithNumber) {
startParam = i + 1;
int index = line.indexOf(',', startExpr);
if (index != -1) {
endParam = index;
}
}
break;
} else {
break;
}
}
}
String paramName = line.substring(startParam, endParam);
if (model.containsKey(paramName)) {
paramName = model.get(paramName);
} else if (!replace) {
paramName = line.substring(startExpr, endExpr + 1);
}
if (!(replace && onlyNumber)) {
newLine.append(paramName);
}
return replace(line, endExpr + 1, model, replace, newLine);
}
return line;
}
}
@@ -1,158 +0,0 @@
/*******************************************************************************
* Copyright (c) 2016-2017 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 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx.customservice;
import java.util.List;
import org.eclipse.lsp4j.Command;
import org.eclipse.lsp4j.MessageType;
import org.eclipse.lsp4j.jsonrpc.json.MessageJsonHandler;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
/**
* An ActionableNotification class.
*
* An instance of this class is sent to the client as a parameter
* for the xml/actionableNotification notification.
*
* See {@link org.eclipse.lemminx.customservice.XMLLanguageClientAPI}
*/
public class ActionableNotification {
/**
* The message type. See {@link MessageType}.
*
*/
@SerializedName("severity")
@Expose
private MessageType severity;
/**
* The actual message
*
*/
@SerializedName("message")
@Expose
private String message;
/**
* Optional data
*
*/
@SerializedName("data")
@Expose
private Object data;
/**
* Optional commands
*
*/
@SerializedName("commands")
@Expose
private List<Command> commands;
/**
* The message severity. See {@link MessageType}.
*
* @return
* The severity
*/
public MessageType getSeverity() {
return severity;
}
/**
* The message severity. See {@link MessageType}.
*
* @param severity
* The message severity
*/
public void setSeverity(MessageType severity) {
this.severity = severity;
}
public ActionableNotification withSeverity(MessageType severity) {
this.severity = severity;
return this;
}
/**
* The actual message
*
* @return
* The message
*/
public String getMessage() {
return message;
}
/**
* The actual message
*
* @param message
* The message
*/
public void setMessage(String message) {
this.message = message;
}
public ActionableNotification withMessage(String message) {
this.message = message;
return this;
}
/**
* @return the data
*/
public Object getData() {
return data;
}
/**
* @param data the data to set
*/
public void setData(Object data) {
this.data = data;
}
public ActionableNotification withData(Object data) {
this.data = data;
return this;
}
/**
* @return the commands
*/
public List<Command> getCommands() {
return commands;
}
/**
* @param commands the commands to set
*/
public void setCommands(List<Command> commands) {
this.commands = commands;
}
public ActionableNotification withCommands(List<Command> commands) {
this.commands = commands;
return this;
}
@Override
public String toString() {
return MessageJsonHandler.toString(this);
}
}
@@ -1,27 +0,0 @@
/*******************************************************************************
* Copyright (c) 2019 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
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors: Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx.customservice;
import org.eclipse.lsp4j.Range;
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;
}
}
@@ -1,52 +0,0 @@
/**
* Copyright (c) 2020 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
* http://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
*/
package org.eclipse.lemminx.customservice;
import java.util.concurrent.CompletableFuture;
import org.eclipse.lsp4j.ExecuteCommandParams;
import org.eclipse.lsp4j.jsonrpc.services.JsonNotification;
import org.eclipse.lsp4j.jsonrpc.services.JsonRequest;
import org.eclipse.lsp4j.jsonrpc.services.JsonSegment;
import org.eclipse.lsp4j.services.LanguageClient;
/**
* XML language client API.
*
*/
@JsonSegment("xml")
public interface XMLLanguageClientAPI extends LanguageClient {
/**
* Notification to be sent to the client with a list of commands
*
* @param command
*/
@JsonNotification("actionableNotification")
default void actionableNotification(ActionableNotification command) {
throw new UnsupportedOperationException();
}
/**
* Executes the command on the client which gives an opportunity to execute a
* command registered by a different Language Server
*
* @param params command execution parameters
* @return the result of the command execution
*/
@JsonRequest("executeClientCommand")
default CompletableFuture<Object> executeClientCommand(ExecuteCommandParams params) {
throw new UnsupportedOperationException();
}
}
@@ -1,36 +0,0 @@
/**
* Copyright (c) 2018 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
* http://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
*/
package org.eclipse.lemminx.customservice;
import java.util.concurrent.CompletableFuture;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.TextDocumentPositionParams;
import org.eclipse.lsp4j.jsonrpc.services.JsonRequest;
import org.eclipse.lsp4j.jsonrpc.services.JsonSegment;
/**
* XML custom services.
*
*/
@JsonSegment("xml")
public interface XMLLanguageServerAPI {
@JsonRequest
CompletableFuture<AutoCloseTagResponse> closeTag(TextDocumentPositionParams params);
@JsonRequest
CompletableFuture<Position> matchingTagPosition(TextDocumentPositionParams params);
}
@@ -1,412 +0,0 @@
/**
* Copyright (c) 2018 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
* http://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
*/
package org.eclipse.lemminx.dom;
import java.util.List;
import org.eclipse.lemminx.utils.StringUtils;
import org.w3c.dom.DOMException;
import org.w3c.dom.Node;
import org.w3c.dom.TypeInfo;
/**
* An attribute node.
*
*/
public class DOMAttr extends DOMNode implements org.w3c.dom.Attr {
private final String name;
private final DOMNode nodeAttrName;
private DOMNode nodeAttrValue;
private String quotelessValue;// Value without quotes
private String originalValue;// Exact value from document
private final DOMNode ownerElement;
private boolean hasDelimiter; // has '='
public static final String XMLNS_ATTR = "xmlns";
public static final String XMLNS_NO_DEFAULT_ATTR = "xmlns:";
class AttrNameOrValue extends DOMNode {
public AttrNameOrValue(int start, int end) {
super(start, end);
}
@Override
public String getNodeName() {
return null;
}
@Override
public short getNodeType() {
return -1;
}
public DOMAttr getOwnerAttr() {
return DOMAttr.this;
}
@Override
public DOMNode getParentNode() {
return DOMAttr.this;
}
@Override
public DOMDocument getOwnerDocument() {
return getOwnerAttr().getOwnerDocument();
}
}
public DOMAttr(String name, DOMNode ownerElement) {
this(name, -1, -1, ownerElement);
}
public DOMAttr(String name, int start, int end, DOMNode ownerElement) {
super(-1, -1);
this.name = name;
this.nodeAttrName = start != -1 ? new AttrNameOrValue(start, end) : null;
this.ownerElement = ownerElement;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getNodeType()
*/
@Override
public short getNodeType() {
return DOMNode.ATTRIBUTE_NODE;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getNodeName()
*/
@Override
public String getNodeName() {
return getName();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Attr#getName()
*/
@Override
public String getName() {
return name;
}
@Override
public String getNodeValue() throws DOMException {
return getValue();
}
@Override
public String getLocalName() {
int colonIndex = name.indexOf(":");
if (colonIndex > 0) {
return name.substring(colonIndex + 1);
}
return name;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Attr#getOwnerElement()
*/
public DOMElement getOwnerElement() {
return ownerElement.isElement() ? (DOMElement) ownerElement : null;
}
@Override
public DOMDocument getOwnerDocument() {
return ownerElement != null ? ownerElement.getOwnerDocument() : null;
}
/*
*
* Returns the attribute's value without quotes.
*/
@Override
public String getValue() {
return quotelessValue;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Attr#getSchemaTypeInfo()
*/
@Override
public TypeInfo getSchemaTypeInfo() {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Attr#getSpecified()
*/
@Override
public boolean getSpecified() {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Attr#isId()
*/
@Override
public boolean isId() {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Attr#setValue(java.lang.String)
*/
@Override
public void setValue(String value) throws DOMException {
setValue(value, -1, -1);
}
public DOMNode getNodeAttrName() {
return nodeAttrName;
}
public void setDelimiter(boolean hasDelimiter) {
this.hasDelimiter = hasDelimiter;
}
public boolean hasDelimiter() {
return this.hasDelimiter;
}
/**
* Get original attribute value from the document.
*
* This will include quotations (", ').
*
* @return attribute value with quotations if it had them.
*/
public String getOriginalValue() {
return originalValue;
}
public void setValue(String value, int start, int end) {
this.originalValue = value;
this.quotelessValue = StringUtils.convertToQuotelessValue(value);
this.nodeAttrValue = start != -1 ? new AttrNameOrValue(start, end) : null;
}
public DOMNode getNodeAttrValue() {
return nodeAttrValue;
}
public void setNodeAttrValue(DOMNode nodeAttrValue) {
this.nodeAttrValue = nodeAttrValue;
}
public boolean valueContainsOffset(int offset) {
return nodeAttrValue != null && offset >= nodeAttrValue.getStart() && offset < nodeAttrValue.getEnd();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getPrefix()
*/
@Override
public String getPrefix() {
String name = getName();
if (name == null) {
return null;
}
String prefix = null;
int index = name.indexOf(":"); //$NON-NLS-1$
if (index != -1) {
prefix = name.substring(0, index);
}
return prefix;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getNamespaceURI()
*/
@Override
public String getNamespaceURI() {
if (ownerElement == null || ownerElement.getNodeType() != Node.ELEMENT_NODE) {
return null;
}
String prefix = getPrefix();
// Try to get xmlns attribute from the element
return ((DOMElement) ownerElement).getNamespaceURI(prefix);
}
/**
* Returns true if attribute name is a xmlns attribute and false otherwise.
*
* @param attributeName
* @return true if attribute name is a xmlns attribute and false otherwise.
*/
public boolean isXmlns() {
return isXmlns(name);
}
public static boolean isXmlns(String attributeName) {
return attributeName.startsWith(XMLNS_ATTR);
}
/**
* Returns true if attribute name is the default xmlns attribute and false
* otherwise.
*
* @param attributeName
* @return true if attribute name is the default xmlns attribute and false
* otherwise.
*/
public boolean isDefaultXmlns() {
return isDefaultXmlns(name);
}
public static boolean isDefaultXmlns(String attributeName) {
return attributeName.equals(XMLNS_ATTR);
}
public String extractPrefixFromXmlns() {
if (isDefaultXmlns()) {
return name.substring(XMLNS_ATTR.length(), name.length());
}
return name.substring(XMLNS_NO_DEFAULT_ATTR.length(), name.length());
}
/**
* Returns the prefix if the given URI matches this attributes value.
*
* If the URI doesnt match, null is returned.
*
* @param uri
* @return
*/
public String getPrefixIfMatchesURI(String uri) {
if (isXmlns()) {
if (quotelessValue != null && quotelessValue.equals(uri)) {
if (isDefaultXmlns()) {
// xmlns="http://"
return null;
}
// xmlns:xxx="http://"
return extractPrefixFromXmlns();
}
}
return null;
}
/**
* Returns true if attribute name is the no default xmlns attribute and false
* otherwise.
*
* @param attributeName
* @return true if attribute name is the no default xmlns attribute and false
* otherwise.
*/
public boolean isNoDefaultXmlns() {
return isNoDefaultXmlns(name);
}
public static boolean isNoDefaultXmlns(String attributeName) {
return attributeName.startsWith(XMLNS_NO_DEFAULT_ATTR);
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getNextSibling()
*/
@Override
public DOMNode getNextSibling() {
DOMNode parentNode = getOwnerElement();
if (parentNode == null) {
return null;
}
List<DOMAttr> children = parentNode.getAttributeNodes();
int nextIndex = children.indexOf(this) + 1;
return nextIndex < children.size() ? children.get(nextIndex) : null;
}
public boolean isIncluded(int offset) {
return DOMNode.isIncluded(getStart(), getEnd(), offset);
}
@Override
public int getStart() {
return nodeAttrName.start;
}
@Override
public int getEnd() {
return nodeAttrValue != null ? nodeAttrValue.end : nodeAttrName.end;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((quotelessValue == null) ? 0 : quotelessValue.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
DOMAttr other = (DOMAttr) obj;
if (name == null) {
if (other.name != null) {
return false;
}
} else if (!name.equals(other.name)) {
return false;
}
if (quotelessValue == null) {
if (other.quotelessValue != null) {
return false;
}
} else if (!quotelessValue.equals(other.quotelessValue)) {
return false;
}
return true;
}
}
@@ -1,49 +0,0 @@
/**
* Copyright (c) 2018 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
* http://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
*/
package org.eclipse.lemminx.dom;
import org.w3c.dom.CDATASection;
/**
* A CData section node.
*
*/
public class DOMCDATASection extends DOMText implements CDATASection {
int startContent;
int endContent;
public DOMCDATASection(int start, int end) {
super(start, end);
}
@Override
public int getStartContent() {
return startContent;
}
@Override
public int getEndContent() {
return endContent;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.w3c.dom.Text#getNodeType()
*/
@Override
public short getNodeType() {
return DOMNode.CDATA_SECTION_NODE;
}
}
@@ -1,243 +0,0 @@
/**
* Copyright (c) 2018 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
* http://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
*/
package org.eclipse.lemminx.dom;
import static java.lang.System.lineSeparator;
import java.util.List;
import org.eclipse.lemminx.commons.BadLocationException;
import org.eclipse.lemminx.utils.StringUtils;
import org.w3c.dom.DOMException;
/**
* A CharacterData node.
*
*/
public abstract class DOMCharacterData extends DOMNode implements org.w3c.dom.CharacterData {
private String data;
private String normalizedData;
private boolean isWhitespace;
private String delimiter;
public DOMCharacterData(int start, int end) {
super(start, end);
}
public boolean hasMultiLine() {
return getData().contains(getDelimiter());
}
public String getDelimiter() {
if (delimiter != null) {
return delimiter;
}
try {
delimiter = getOwnerDocument().getTextDocument().lineDelimiter(0);
return delimiter;
} catch (BadLocationException e) {
delimiter = lineSeparator();
return delimiter;
}
}
/**
* If data ends with a new line character.
*
* Returns false if a character is found before a new line. Non-newline
* whitespace will be ignored while searching.
*
* If no data exists, returns false.
*
* @return true if newline character ocurrs before non-whitespace character
*/
public boolean endsWithNewLine() {
if (hasData()) {
for (int i = data.length() - 1; i >= 0; i--) {
char c = data.charAt(i);
if (!Character.isWhitespace(c)) {
return false;
}
if (c == '\n') {
return true;
}
}
}
return false;
}
/**
* If data ends with a new line character.
*
* Returns false if a character is found before a new line. Non-newline
* whitespace will be ignored while searching.
*
* @return true if newline character ocurrs before non-whitespace character
*/
public boolean startsWithNewLine() {
if (hasData()) {
for (int i = 0; i < data.length(); i++) {
char c = data.charAt(i);
if (!Character.isWhitespace(c)) {
return false;
}
if (c == '\n' || c == '\r') {
return true;
}
}
}
return false;
}
public String getNormalizedData() {
if (normalizedData == null) {
normalizedData = StringUtils.normalizeSpace(getData());
}
return normalizedData;
}
public boolean hasData() {
return !getData().isEmpty();
}
/**
* Returns true if this node has sibling nodes.
*/
public boolean hasSiblings() {
List<DOMNode> childrenOfParent = this.parent.getChildren();
return childrenOfParent.size() > 1;
}
public int getStartContent() {
return start;
}
public int getEndContent() {
return end;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.CharacterData#getData()
*/
@Override
public String getData() {
if (data == null) {
data = getOwnerDocument().getText().substring(getStartContent(), getEndContent());
}
return data;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getNodeValue()
*/
@Override
public String getNodeValue() throws DOMException {
return getData();
}
/**
* @return the isWhitespace
*/
public boolean isWhitespace() {
return isWhitespace;
}
/**
* Set true if this node's data is all whitespace
*
* @param isWhitespace
*/
public void setWhitespace(boolean isWhitespace) {
this.isWhitespace = isWhitespace;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.CharacterData#appendData(java.lang.String)
*/
@Override
public void appendData(String data) throws DOMException {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.CharacterData#deleteData(int, int)
*/
@Override
public void deleteData(int offset, int count) throws DOMException {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.CharacterData#getLength()
*/
@Override
public int getLength() {
return getData().length();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.CharacterData#insertData(int, java.lang.String)
*/
@Override
public void insertData(int offset, String data) throws DOMException {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.CharacterData#replaceData(int, int, java.lang.String)
*/
@Override
public void replaceData(int offset, int count, String data) throws DOMException {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.CharacterData#setData(java.lang.String)
*/
@Override
public void setData(String value) throws DOMException {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.CharacterData#substringData(int, int)
*/
@Override
public String substringData(int offset, int count) throws DOMException {
throw new UnsupportedOperationException();
}
}
@@ -1,64 +0,0 @@
/**
* Copyright (c) 2018 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
* http://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
*/
package org.eclipse.lemminx.dom;
/**
* A Comment node.
*
*/
public class DOMComment extends DOMCharacterData implements org.w3c.dom.Comment {
boolean commentSameLineEndTag;
int startContent;
int endContent;
public DOMComment(int start, int end) {
super(start, end);
}
public boolean isCommentSameLineEndTag() {
return commentSameLineEndTag;
}
@Override
public int getStartContent() {
return startContent;
}
@Override
public int getEndContent() {
return endContent;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getNodeType()
*/
@Override
public short getNodeType() {
return DOMNode.COMMENT_NODE;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getNodeName()
*/
@Override
public String getNodeName() {
return "#comment";
}
}
@@ -1,947 +0,0 @@
/**
* Copyright (c) 2018-2020 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
* http://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Initial code from https://github.com/Microsoft/vscode-html-languageservice
* Initial copyright Copyright (C) Microsoft Corporation. All rights reserved.
* Initial license: MIT
*
* Contributors:
* - Microsoft Corporation: Initial code, written in TypeScript, licensed under MIT license
* - Angelo Zerr <angelo.zerr@gmail.com> - translation and adaptation to Java
*/
package org.eclipse.lemminx.dom;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.eclipse.lemminx.commons.BadLocationException;
import org.eclipse.lemminx.commons.TextDocument;
import org.eclipse.lemminx.dom.parser.Constants;
import org.eclipse.lemminx.uriresolver.URIResolverExtensionManager;
import org.eclipse.lemminx.utils.DOMUtils;
import org.eclipse.lemminx.utils.StringUtils;
import org.eclipse.lemminx.utils.XMLPositionUtility;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
import org.w3c.dom.CDATASection;
import org.w3c.dom.DOMConfiguration;
import org.w3c.dom.DOMException;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentFragment;
import org.w3c.dom.EntityReference;
import org.w3c.dom.NodeList;
/**
* XML document.
*
*/
public class DOMDocument extends DOMNode implements Document {
private SchemaLocation schemaLocation;
private NoNamespaceSchemaLocation noNamespaceSchemaLocation;
private List<XMLModel> xmlModels;
private boolean referencedExternalGrammarInitialized;
private boolean referencedSchemaInitialized;
private final URIResolverExtensionManager resolverExtensionManager;
private final TextDocument textDocument;
private boolean hasNamespaces;
private Map<String, String> externalGrammarLocation;
private String schemaInstancePrefix;
private String schemaPrefix;
private CancelChecker cancelChecker;
private String externalGrammarFromNamespaceURI;
public DOMDocument(TextDocument textDocument, URIResolverExtensionManager resolverExtensionManager) {
super(0, textDocument.getText().length());
this.textDocument = textDocument;
this.resolverExtensionManager = resolverExtensionManager;
resetGrammar();
}
public void setCancelChecker(CancelChecker cancelChecker) {
this.cancelChecker = cancelChecker;
}
public CancelChecker getCancelChecker() {
return cancelChecker;
}
public List<DOMNode> getRoots() {
return super.getChildren();
}
public Position positionAt(int offset) throws BadLocationException {
checkCanceled();
return textDocument.positionAt(offset);
}
public int offsetAt(Position position) throws BadLocationException {
checkCanceled();
return textDocument.offsetAt(position);
}
public String lineText(int lineNumber) throws BadLocationException {
checkCanceled();
return textDocument.lineText(lineNumber);
}
public String lineDelimiter(int lineNumber) throws BadLocationException {
checkCanceled();
return textDocument.lineDelimiter(lineNumber);
}
public LineIndentInfo getLineIndentInfo(int lineNumber) throws BadLocationException {
String lineText = lineText(lineNumber);
String lineDelimiter = lineDelimiter(lineNumber);
String whitespacesIndent = StringUtils.getStartWhitespaces(lineText);
return new LineIndentInfo(lineDelimiter, whitespacesIndent);
}
/**
* Returns the element name on the left of the given position and null
* otherwise.
*
* @param textOffset
* @return the element name on the left of the given position and null
* otherwise.
*/
public Range getElementNameRangeAt(int textOffset) {
checkCanceled();
return textDocument.getWordRangeAt(textOffset, Constants.ELEMENT_NAME_REGEX);
}
@Override
public String getNamespaceURI() {
DOMElement documentElement = getDocumentElement();
return documentElement != null ? documentElement.getNamespaceURI() : null;
}
/**
* Returns the text content of the XML document.
*
* @return the text content of the XML document.
*/
public String getText() {
return textDocument.getText();
}
public TextDocument getTextDocument() {
return textDocument;
}
/**
* Returns true if the document is bound to a grammar and false otherwise.
*
* @return true if the document is bound to a grammar and false otherwise.
*/
public boolean hasGrammar() {
return hasDTD() || hasSchemaLocation() || hasNoNamespaceSchemaLocation() || hasExternalGrammar()
|| hasXMLModel();
}
// -------------------------- Grammar with XML Schema
/**
* Returns the declared "xsi:schemaLocation" and null otherwise.
*
* @return the declared "xsi:schemaLocation" and null otherwise.
*/
public SchemaLocation getSchemaLocation() {
initializeReferencedSchemaIfNeeded();
return schemaLocation;
}
/**
* Returns true if XML root element declares a "xsi:schemaLocation" and false
* otherwise.
*
* @return true if XML root element declares a "xsi:schemaLocation" and false
* otherwise.
*/
public boolean hasSchemaLocation() {
return getSchemaLocation() != null;
}
/**
* Returns the declared "xsi:noNamespaceSchemaLocation" and null otherwise.
*
* @return the declared "xsi:noNamespaceSchemaLocation" and null otherwise.
*/
public NoNamespaceSchemaLocation getNoNamespaceSchemaLocation() {
initializeReferencedSchemaIfNeeded();
return noNamespaceSchemaLocation;
}
/**
* Returns true if XML root element declares a "xsi:noNamespaceSchemaLocation"
* and false otherwise.
*
* @return true if XML root element declares a "xsi:noNamespaceSchemaLocation"
* and false otherwise.
*/
public boolean hasNoNamespaceSchemaLocation() {
return getNoNamespaceSchemaLocation() != null;
}
/**
* Returns true if document defines namespaces (with xmlns) and false otherwise.
*
* @return true if document defines namespaces (with xmlns) and false otherwise.
*/
public boolean hasNamespaces() {
initializeReferencedSchemaIfNeeded();
return hasNamespaces;
}
/**
* Returns the (xsi) schema instance prefix and null otherwise.
*
* @return the (xsi) schema instance prefix and null otherwise.
*/
public String getSchemaInstancePrefix() {
initializeReferencedSchemaIfNeeded();
return schemaInstancePrefix;
}
/**
* Returns true if (xsi) schema instance prefix exists.
*
* @return true if (xsi) schema instance prefix exists.
*/
public boolean hasSchemaInstancePrefix() {
initializeReferencedSchemaIfNeeded();
return schemaInstancePrefix != null;
}
/**
* Initialize schemaLocation, noNamespaceSchemaLocation and hasNamespaces
* information if needed.
*/
private void initializeReferencedSchemaIfNeeded() {
if (referencedSchemaInitialized) {
return;
}
initializeReferencedSchema();
referencedSchemaInitialized = true;
}
/**
* Initialize namespaces and schema location declaration .
*/
private synchronized void initializeReferencedSchema() {
if (referencedSchemaInitialized) {
return;
}
// xml-model
xmlModels = XMLModel.createXMLModels(this);
// Get root element
DOMElement documentElement = getDocumentElement();
if (documentElement == null) {
return;
}
schemaInstancePrefix = null;
schemaPrefix = null;
// Search if document element root declares namespace with "xmlns".
if (documentElement.hasAttributes()) {
for (DOMAttr attr : documentElement.getAttributeNodes()) {
String attributeName = attr.getName();
if (attributeName != null) {
if (attributeName.equals(DOMAttr.XMLNS_ATTR)
|| attributeName.startsWith(DOMAttr.XMLNS_NO_DEFAULT_ATTR)) {
hasNamespaces = true;
String attributeValue = documentElement.getAttribute(attributeName);
if (attributeValue != null && attributeValue.startsWith("http://www.w3.org/")) {
if (attributeValue.endsWith("/XMLSchema-instance")) {
schemaInstancePrefix = attributeName.equals("xmlns") ? ""
: getUnprefixedName(attributeName);
} else if (attributeValue.endsWith("/XMLSchema")) {
schemaPrefix = attributeName.equals("xmlns") ? "" : getUnprefixedName(attributeName);
}
}
}
}
}
if (schemaInstancePrefix != null) {
// DOM document can declared xsi:noNamespaceSchemaLocation and
// xsi:schemaLocation both even it's not valid
noNamespaceSchemaLocation = createNoNamespaceSchemaLocation(documentElement, schemaInstancePrefix);
schemaLocation = createSchemaLocation(documentElement, schemaInstancePrefix);
}
}
}
/**
* If document has {@code <?xml ... ?>}
*
* @return
*/
public boolean hasProlog() {
List<DOMNode> children = getChildren();
return (children != null && !children.isEmpty() && children.get(0).isProlog());
}
/**
* If document has {@code <?xml ... ?>}, return this prolog, null otherwise
*
* @return prolog DOMNode, if the document has one, null otherwise
*/
public DOMNode getProlog() {
if (hasProlog()) {
return getChild(0);
} else {
return null;
}
}
/**
* Returns true, if the given offset is before XML declaration
* ({@code <?xml ...?>}), false otherwise.
*
* @param offset the offset position in question
* @return true, if before xml declaration; false otherwise
*/
public boolean isBeforeProlog(int offset) {
return hasProlog() && offset <= getProlog().getStart();
}
private static SchemaLocation createSchemaLocation(DOMNode root, String schemaInstancePrefix) {
DOMAttr attr = root.getAttributeNode(getPrefixedName(schemaInstancePrefix, "schemaLocation"));
if (attr == null) {
return null;
}
return new SchemaLocation(attr);
}
private static NoNamespaceSchemaLocation createNoNamespaceSchemaLocation(DOMNode root,
String schemaInstancePrefix) {
DOMAttr attr = root.getAttributeNode(getPrefixedName(schemaInstancePrefix, "noNamespaceSchemaLocation"));
if (attr == null || attr.getValue() == null) {
return null;
}
return new NoNamespaceSchemaLocation(attr);
}
// -------------------------- Grammar with DTD
/**
* Returns true if XML document has a DTD declaration and false otherwise.
*
* @return true if XML document has a DTD declaration and false otherwise.
*/
public boolean hasDTD() {
return getDoctype() != null;
}
// -------------------------- Grammar with <?xml-model processing instruction
/**
* Returns true if XML document has a xml-model processing declaration and false
* otherwise.
*
* @return true if XML document has a xml-model processing declaration and false
* otherwise.
*/
public boolean hasXMLModel() {
return !getXMLModels().isEmpty();
}
/**
* Returns the list of xml-model processing instruction declared in the
* document.
*
* @return the list of xml-model processing instruction declared in the
* document.
*/
public List<XMLModel> getXMLModels() {
initializeReferencedSchemaIfNeeded();
return xmlModels;
}
// -------------------------- External Grammar (XML file associations, catalog)
/**
* Returns true if the document is bound to an external grammar (XML file
* associations, XLM catalog) and false otherwise.
*
* @return true if the document is bound to an external grammar (XML file
* associations, XLM catalog) and false otherwise.
*/
public boolean hasExternalGrammar() {
return getExternalGrammarLocation() != null || getExternalGrammarFromNamespaceURI() != null;
}
/**
* Returns the external grammar location (XSD, DTD from xml file associations)
* and null otherwise.
*
* @return the external grammar location (XSD, DTD from xml file associations)
* and null otherwise.
*/
public Map<String, String> getExternalGrammarLocation() {
initializeReferencedExternalGrammarIfNeeded();
return externalGrammarLocation;
}
/**
* Returns the grammar location found by the namespace URI from the document
* root element (ex : found with XML catalog) and null otherwise.
*
* @return the grammar location found by the namespace URI from the document
* root element (ex : found with XML catalog) and null otherwise.
*/
public String getExternalGrammarFromNamespaceURI() {
initializeReferencedExternalGrammarIfNeeded();
return externalGrammarFromNamespaceURI;
}
private void initializeReferencedExternalGrammarIfNeeded() {
if (referencedExternalGrammarInitialized) {
return;
}
intializeExternalGrammar();
}
private synchronized void intializeExternalGrammar() {
if (referencedExternalGrammarInitialized) {
return;
}
try {
if (resolverExtensionManager != null) {
// No grammar found with standard mean, check if it some components like XML
// file associations bind this XML document to a grammar with external schema
// location.
try {
externalGrammarLocation = resolverExtensionManager
.getExternalGrammarLocation(new URI(getDocumentURI()));
if (externalGrammarLocation != null) {
return;
}
} catch (URISyntaxException e) {
// Do nothing
}
// No grammar found with standard mean and external schema location, check if
// it some components like XML
// Catalog, XSL and XSD resolvers, etc bind this XML document to a grammar.
// Get root element
DOMElement documentElement = getDocumentElement();
if (documentElement == null) {
return;
}
String namespaceURI = documentElement.getNamespaceURI();
externalGrammarFromNamespaceURI = resolverExtensionManager.resolve(getDocumentURI(), namespaceURI,
null);
}
} finally {
referencedExternalGrammarInitialized = true;
}
}
private static String getUnprefixedName(String name) {
int index = name.indexOf(":"); //$NON-NLS-1$
if (index != -1) {
name = name.substring(index + 1);
}
return name;
}
private static String getPrefixedName(String prefix, String localName) {
return prefix != null && prefix.length() > 0 ? prefix + ":" + localName : localName; //$NON-NLS-1$
}
public DOMElement createElement(int start, int end) {
return new DOMElement(start, end);
}
public DOMCDATASection createCDataSection(int start, int end) {
return new DOMCDATASection(start, end);
}
public DOMProcessingInstruction createProcessingInstruction(int start, int end) {
return new DOMProcessingInstruction(start, end);
}
public DOMComment createComment(int start, int end) {
return new DOMComment(start, end);
}
public DOMText createText(int start, int end) {
return new DOMText(start, end);
}
public DOMDocumentType createDocumentType(int start, int end) {
return new DOMDocumentType(start, end);
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getNodeType()
*/
@Override
public short getNodeType() {
return DOMNode.DOCUMENT_NODE;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getNodeName()
*/
@Override
public String getNodeName() {
return "#document";
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#getDocumentElement()
*/
@Override
public DOMElement getDocumentElement() {
List<DOMNode> roots = getRoots();
if (roots != null) {
for (DOMNode node : roots) {
if (node.isElement()) {
return (DOMElement) node;
}
}
}
return null;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#getDoctype()
*/
@Override
public DOMDocumentType getDoctype() {
List<DOMNode> roots = getRoots();
if (roots != null) {
for (DOMNode node : roots) {
if (node.isDoctype()) {
return (DOMDocumentType) node;
}
}
}
return null;
}
@Override
public DOMDocument getOwnerDocument() {
return this;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#getDocumentURI()
*/
@Override
public String getDocumentURI() {
return textDocument.getUri();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#setDocumentURI(java.lang.String)
*/
@Override
public void setDocumentURI(String documentURI) {
textDocument.setUri(documentURI);
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#adoptNode(org.w3c.dom.Node)
*/
@Override
public DOMNode adoptNode(org.w3c.dom.Node source) throws DOMException {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#createAttribute(java.lang.String)
*/
@Override
public DOMAttr createAttribute(String name) throws DOMException {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#createAttributeNS(java.lang.String,
* java.lang.String)
*/
@Override
public DOMAttr createAttributeNS(String namespaceURI, String qualifiedName) throws DOMException {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#createCDATASection(java.lang.String)
*/
@Override
public CDATASection createCDATASection(String data) throws DOMException {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#createComment(java.lang.String)
*/
@Override
public DOMComment createComment(String data) {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#createDocumentFragment()
*/
@Override
public DocumentFragment createDocumentFragment() {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#createElement(java.lang.String)
*/
@Override
public DOMElement createElement(String tagName) throws DOMException {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#createElementNS(java.lang.String, java.lang.String)
*/
@Override
public DOMElement createElementNS(String namespaceURI, String qualifiedName) throws DOMException {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#createEntityReference(java.lang.String)
*/
@Override
public EntityReference createEntityReference(String name) throws DOMException {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#createProcessingInstruction(java.lang.String,
* java.lang.String)
*/
@Override
public DOMProcessingInstruction createProcessingInstruction(String target, String data) throws DOMException {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#createTextNode(java.lang.String)
*/
@Override
public DOMText createTextNode(String data) {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#getDomConfig()
*/
@Override
public DOMConfiguration getDomConfig() {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#getElementById(java.lang.String)
*/
@Override
public DOMElement getElementById(String elementId) {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#getElementsByTagName(java.lang.String)
*/
@Override
public NodeList getElementsByTagName(String tagname) {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#getElementsByTagNameNS(java.lang.String,
* java.lang.String)
*/
@Override
public NodeList getElementsByTagNameNS(String namespaceURI, String localName) {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#getImplementation()
*/
@Override
public DOMImplementation getImplementation() {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#getInputEncoding()
*/
@Override
public String getInputEncoding() {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#getStrictErrorChecking()
*/
@Override
public boolean getStrictErrorChecking() {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#getXmlEncoding()
*/
@Override
public String getXmlEncoding() {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#getXmlStandalone()
*/
@Override
public boolean getXmlStandalone() {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#getXmlVersion()
*/
@Override
public String getXmlVersion() {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#importNode(org.w3c.dom.Node, boolean)
*/
@Override
public DOMNode importNode(org.w3c.dom.Node importedNode, boolean deep) throws DOMException {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#normalizeDocument()
*/
@Override
public void normalizeDocument() {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#renameNode(org.w3c.dom.Node, java.lang.String,
* java.lang.String)
*/
@Override
public DOMNode renameNode(org.w3c.dom.Node n, String namespaceURI, String qualifiedName) throws DOMException {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#setStrictErrorChecking(boolean)
*/
@Override
public void setStrictErrorChecking(boolean strictErrorChecking) {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#setXmlStandalone(boolean)
*/
@Override
public void setXmlStandalone(boolean xmlStandalone) throws DOMException {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Document#setXmlVersion(java.lang.String)
*/
@Override
public void setXmlVersion(String xmlVersion) throws DOMException {
throw new UnsupportedOperationException();
}
/**
* Reset the cached grammar flag.
*/
public void resetGrammar() {
this.referencedExternalGrammarInitialized = false;
this.referencedSchemaInitialized = false;
}
public URIResolverExtensionManager getResolverExtensionManager() {
return resolverExtensionManager;
}
/**
* Returns true if the XML document is a DTD and false otherwise.
*
* @return true if the XML document is a DTD and false otherwise.
*/
public boolean isDTD() {
String uri = this.getDocumentURI();
if (DOMUtils.isDTD(uri)) {
return true;
}
return false;
}
/**
* Returns true if 'offset' is within an internal DOCTYPE dtd. Else false.
*
* @param offset
* @return
*/
public boolean isWithinInternalDTD(int offset) {
DOMDocumentType doctype = this.getDoctype();
if (doctype != null && doctype.internalSubset != null) {
return offset > doctype.internalSubset.start && offset < doctype.internalSubset.end;
}
return false;
}
public Range getTrimmedRange(Range range) {
if (range != null) {
return getTrimmedRange(range.getStart().getCharacter(), range.getEnd().getCharacter());
}
return null;
}
public Range getTrimmedRange(int start, int end) {
String text = getText();
char c = text.charAt(start);
while (Character.isWhitespace(c)) {
start++;
c = text.charAt(start);
}
if (start == end) {
return null;
}
end--;
c = text.charAt(end);
while (Character.isWhitespace(c)) {
end--;
c = text.charAt(end);
}
end++;
return XMLPositionUtility.createRange(start, end, this);
}
/**
* Returns the DTD Attribute list for the given element name and empty
* otherwise.
*
* @param elementName
* @return the DTD Attribute list for the given element name and empty
* otherwise.
*/
public Collection<DOMNode> findDTDAttrList(String elementName) {
DOMDocumentType docType = getDoctype();
if (docType == null || elementName == null) {
return Collections.emptyList();
}
return docType.getChildren().stream().filter(DOMNode::isDTDAttListDecl)
.filter(n -> elementName.equals(((DTDAttlistDecl) n).getElementName())).collect(Collectors.toList());
}
/**
* Returns the XML Schema prefix (ex : 'xs' for
* xmlns:xs="http://www.w3.org/2001/XMLSchema")
*
* @return the XML Schema prefix (ex : 'xs' for
* xmlns:xs="http://www.w3.org/2001/XMLSchema")
*/
public String getSchemaPrefix() {
initializeReferencedSchemaIfNeeded();
return schemaPrefix;
}
private void checkCanceled() {
if (cancelChecker != null) {
cancelChecker.checkCanceled();
}
}
}
@@ -1,232 +0,0 @@
/**
* Copyright (c) 2018-2020 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
* http://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
*/
package org.eclipse.lemminx.dom;
import java.util.List;
import org.w3c.dom.NamedNodeMap;
/**
* A doctype node.
*
*/
public class DOMDocumentType extends DTDDeclNode implements org.w3c.dom.DocumentType {
public static enum DocumentTypeKind {
PUBLIC, SYSTEM, INVALID
}
private XMLNamedNodeMap<DTDEntityDecl> entitiesNodes;
DTDDeclParameter kind; // SYSTEM || PUBLIC
DTDDeclParameter publicId;
DTDDeclParameter systemId;
DTDDeclParameter internalSubset;
private String content; // |<!DOCTYPE ... >|
public DOMDocumentType(int start, int end) {
super(start, end);
}
@Override
public DOMDocumentType getOwnerDocType() {
return this;
}
@Override
public String getTextContent() {
if (content == null) {
content = getOwnerDocument().getText().substring(getStart(), getEnd());
}
return content;
}
/**
* Returns the document type kind (PUBLIC or SYSTEM)
*
* @return the document type kind (PUBLIC or SYSTEM)
*/
public String getKind() {
return kind != null ? kind.getParameter() : null;
}
/**
* Returns the node where document type kind (PUBLIC or SYSTEM) is declared
*
* @return the node where document type kind (PUBLIC or SYSTEM) is declared
*/
public DTDDeclParameter getKindNode() {
return kind;
}
/**
* @param kind the DocumentTypeKind to set
*/
void setKind(int start, int end) {
kind = addNewParameter(start, end);
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getNodeName()
*/
@Override
public String getNodeName() {
return getName();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getNodeType()
*/
@Override
public short getNodeType() {
return DOMNode.DOCUMENT_TYPE_NODE;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.DocumentType#getEntities()
*/
@Override
public NamedNodeMap getEntities() {
if (entitiesNodes == null) {
entitiesNodes = new XMLNamedNodeMap<>();
List<DOMNode> children = super.getChildren();
for (DOMNode child : children) {
if (child.getNodeType() == DOMNode.ENTITY_NODE) {
entitiesNodes.add((DTDEntityDecl) child);
}
}
}
return entitiesNodes;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.DocumentType#getInternalSubset()
*/
@Override
public String getInternalSubset() {
if (internalSubset != null) {
// Returns subset without '[' and ']'
return internalSubset.getParameterWithoutFirstAndLastChar();
}
return null;
}
public void setStartInternalSubset(int start) {
internalSubset = addNewParameter(start, start + 1);
}
public void setEndInternalSubset(int end) {
updateLastParameterEnd(end);
}
public boolean isInternalSubset(DTDDeclParameter parameter) {
if (this.internalSubset != null) {
return this.internalSubset.equals(parameter);
}
return false;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.DocumentType#getNotations()
*/
@Override
public NamedNodeMap getNotations() {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.DocumentType#getPublicId()
*/
@Override
public String getPublicId() {
return publicId != null ? publicId.getParameter() : null;
}
public String getPublicIdWithoutQuotes() {
return publicId != null ? publicId.getParameterWithoutFirstAndLastChar() : null;
}
public DTDDeclParameter getPublicIdNode() {
return publicId;
}
/**
* @param publicId the publicId to set
*/
void setPublicId(int start, int end) {
publicId = addNewParameter(start, end);
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.DocumentType#getSystemId()
*/
@Override
public String getSystemId() {
return systemId != null ? systemId.getParameter() : null;
}
public DTDDeclParameter getSystemIdNode() {
return systemId;
}
public String getSystemIdWithoutQuotes() {
return systemId != null ? systemId.getParameterWithoutFirstAndLastChar() : null;
}
/**
* @param systemId the systemId to set
*/
void setSystemId(int start, int end) {
systemId = addNewParameter(start, end);
}
/**
* Returns a substring of the whole document.
*
*
* Since offset values are relative to 'this.start' we need to subtract
* getStart() to make them relative to 'content'
*/
public String getSubstring(int start, int end) {
String textContent = getTextContent();
if (textContent == null) {
return null;
}
return textContent.substring(start - getStart(), end - getStart());
}
/**
* Returns the declaration parameter that represents the internal subset, or null if there is no internal subset
*
* @return the declaration parameter that represents the internal subset, or null if there is no internal subset
*/
public DTDDeclParameter getInternalSubsetNode() {
return this.internalSubset;
}
}
@@ -1,567 +0,0 @@
/**
* Copyright (c) 2018 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
* http://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
*/
package org.eclipse.lemminx.dom;
import static org.eclipse.lemminx.dom.DOMAttr.XMLNS_ATTR;
import static org.eclipse.lemminx.dom.DOMAttr.XMLNS_NO_DEFAULT_ATTR;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import org.eclipse.lemminx.utils.StringUtils;
import org.w3c.dom.DOMException;
import org.w3c.dom.NodeList;
import org.w3c.dom.TypeInfo;
/**
* An Element node.
*
*/
public class DOMElement extends DOMNode implements org.w3c.dom.Element {
String tag;
boolean selfClosed;
// DomElement.start == startTagOpenOffset
int startTagOpenOffset = NULL_VALUE; // |<root>
int startTagCloseOffset = NULL_VALUE; // <root |>
int endTagOpenOffset = NULL_VALUE; // <root> |</root >
int endTagCloseOffset = NULL_VALUE;// <root> </root |>
// DomElement.end = <root> </root>| , is always scanner.getTokenEnd()
public DOMElement(int start, int end) {
super(start, end);
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getNodeType()
*/
@Override
public short getNodeType() {
return DOMNode.ELEMENT_NODE;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getNodeName()
*/
@Override
public String getNodeName() {
return getTagName();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Element#getTagName()
*/
@Override
public String getTagName() {
return tag;
}
/**
* Returns true if the DOM element have a tag name and false otherwise (ex : '<'
* or '</').
*
* @return true if the DOM element have a tag name and false otherwise (ex : '<'
* or '</').
*/
public boolean hasTagName() {
return tag != null;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getLocalName()
*/
@Override
public String getLocalName() {
String name = getTagName();
if (name == null) {
return null;
}
int index = name.indexOf(":"); //$NON-NLS-1$
if (index != -1) {
name = name.substring(index + 1);
}
return name;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getPrefix()
*/
@Override
public String getPrefix() {
String name = getTagName();
if (name == null) {
return null;
}
String prefix = null;
int index = name.indexOf(":"); //$NON-NLS-1$
if (index != -1) {
prefix = name.substring(0, index);
}
return prefix;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getNamespaceURI()
*/
@Override
public String getNamespaceURI() {
String prefix = getPrefix();
// Try to get xmlns attribute from the element
return getNamespaceURI(prefix);
}
/**
* Returns the namespace URI for the given prefix and null otherwise.
*
* @param prefix the prefix.
*
* @return the namespace URI for the given prefix and null otherwise.
*/
public String getNamespaceURI(String prefix) {
String namespaceURI = getNamespaceURI(prefix, this);
if (namespaceURI != null) {
return namespaceURI;
}
// try to get the namespace from the parent element
DOMNode parent = getParentNode();
while (parent != null) {
if (parent.getNodeType() == DOMNode.ELEMENT_NODE) {
DOMElement parentElement = ((DOMElement) parent);
namespaceURI = getNamespaceURI(prefix, parentElement);
if (namespaceURI != null) {
return namespaceURI;
}
}
parent = parent.getParentNode();
}
return null;
}
/**
* Returns the namespace URI from the given prefix declared in the given element
* and null otherwise.
*
* @param prefix the prefix
* @param element the DOM element
* @return the namespace URI from the given prefix declared in the given element
* and null otherwise.
*/
private static String getNamespaceURI(String prefix, DOMElement element) {
boolean hasPrefix = !StringUtils.isEmpty(prefix);
return hasPrefix ? element.getAttribute(XMLNS_NO_DEFAULT_ATTR + prefix) : element.getAttribute(XMLNS_ATTR);
}
public Collection<String> getAllPrefixes() {
if (hasAttributes()) {
Collection<String> prefixes = new ArrayList<>();
for (DOMAttr attr : getAttributeNodes()) {
if (attr.isNoDefaultXmlns()) {
prefixes.add(attr.extractPrefixFromXmlns());
}
}
return prefixes;
}
return Collections.emptyList();
}
/**
* Returns the xmlns prefix from the given namespace URI and null otherwise.
*
* @param namespaceURI the namespace
* @return the xmlns prefix from the given namespace URI and null otherwise.
*/
public String getPrefix(String namespaceURI) {
if (namespaceURI == null) {
return null;
}
if (hasAttributes()) {
for (DOMAttr attr : getAttributeNodes()) {
String prefix = attr.getPrefixIfMatchesURI(namespaceURI);
if (prefix != null) {
return prefix;
}
}
}
// try to get the prefix in the parent element
DOMNode parent = getParentNode();
while (parent != null) {
if (parent.getNodeType() == DOMNode.ELEMENT_NODE) {
DOMElement parentElement = ((DOMElement) parent);
String prefix = parentElement.getPrefix(namespaceURI);
if (prefix != null) {
return prefix;
}
}
parent = parent.getParentNode();
}
return null;
}
public boolean isDocumentElement() {
return this.equals(getOwnerDocument().getDocumentElement());
}
public boolean isSelfClosed() {
return selfClosed;
}
/**
* Will traverse backwards from the start offset returning an offset of the
* given character if it's found before another character. Whitespace is
* ignored.
*
* Returns null if the character is not found.
*
* The initial value for the start offset is not included. So have the offset 1
* position after the character you want to start at.
*/
public Integer endsWith(char c, int startOffset) {
String text = this.getOwnerDocument().getText();
if (startOffset > text.length() || startOffset < 0) {
return null;
}
startOffset--;
while (startOffset >= 0) {
char current = text.charAt(startOffset);
if (Character.isWhitespace(current)) {
startOffset--;
continue;
}
if (current != c) {
return null;
}
return startOffset;
}
return null;
}
/**
* Returns true if the given tag is the same tag of this element and false
* otherwise.
*
* @param tag tag element
* @return true if the given tag is the same tag of this element and false
* otherwise.
*/
public boolean isSameTag(String tag) {
return Objects.equals(this.tag, tag);
}
public boolean isInStartTag(int offset) {
if (startTagOpenOffset == NULL_VALUE || startTagCloseOffset == NULL_VALUE) {
// case <|
return true;
}
if (offset > startTagOpenOffset && offset <= startTagCloseOffset) {
// case <bean | >
return true;
}
return false;
}
public boolean isInEndTag(int offset) {
return isInEndTag(offset, false);
}
public boolean isInEndTag(int offset, boolean afterBackSlash) {
if (endTagOpenOffset == NULL_VALUE) {
// case >|
return false;
}
if (offset > endTagOpenOffset + (afterBackSlash ? 1 : 0) && offset < getEnd()) {
// case </bean | >
return true;
}
return false;
}
public boolean isInInsideStartEndTag(int offset) {
return offset > startTagCloseOffset && offset <= endTagOpenOffset;
}
/**
* Returns the start tag open offset and {@link DOMNode#NULL_VALUE} if it
* doesn't exist.
*
* @return the start tag open offset and {@link DOMNode#NULL_VALUE} if it
* doesn't exist.
*/
public int getStartTagOpenOffset() {
return startTagOpenOffset;
}
/**
* Returns the start tag close offset and {@link DOMNode#NULL_VALUE} if it
* doesn't exist.
*
* @return the start tag close offset and {@link DOMNode#NULL_VALUE} if it
* doesn't exist.
*/
public int getStartTagCloseOffset() {
return startTagCloseOffset;
}
/**
* Returns the end tag open offset and {@link DOMNode#NULL_VALUE} if it doesn't
* exist.
*
* @return the end tag open offset and {@link DOMNode#NULL_VALUE} if it doesn't
* exist.
*/
public int getEndTagOpenOffset() {
return endTagOpenOffset;
}
/**
* Returns the end tag close offset and {@link DOMNode#NULL_VALUE} if it doesn't
* exist.
*
* @return the end tag close offset and {@link DOMNode#NULL_VALUE} if it doesn't
* exist.
*/
public int getEndTagCloseOffset() {
return endTagCloseOffset;
}
/**
* Returns true if has a start tag.
*
* In our source-oriented DOM, a lone end tag will cause a node to be created in
* the tree, unlike well-formed-only DOMs.
*
* @return true if has a start tag.
*/
public boolean hasStartTag() {
return getStartTagOpenOffset() != NULL_VALUE;
}
/**
* Returns true if has an end tag.
*
* In our source-oriented DOM, sometimes Elements are "ended", even without an
* explicit end tag in the source.
*
* @return true if has an end tag.
*/
public boolean hasEndTag() {
return getEndTagOpenOffset() != NULL_VALUE;
}
/**
* If '>' exists in <root>
*/
public boolean isStartTagClosed() {
return getStartTagCloseOffset() != NULL_VALUE;
}
/**
* If '>' exists in </root>
*/
public boolean isEndTagClosed() {
return getEndTagCloseOffset() != NULL_VALUE;
}
/**
* Returns true if the given element is an orphan end tag (which has no start
* tag, eg: </a>) and false otherwise.
*
* @return true if the given element is an orphan end tag (which has no start
* tag, eg: </a>) and false otherwise.
*/
public boolean isOrphanEndTag() {
return hasEndTag() && !hasStartTag();
}
/**
* Returns true if the given element is an orphan end tag (which has no start
* tag, eg: </a>) of the given tag name and false otherwise.
*
* @param tagName the end tag name.
* @return true if the given element is an orphan end tag (which has no start
* tag, eg: </a>) of the given tag name and false otherwise.
*/
public boolean isOrphanEndTagOf(String tagName) {
return isSameTag(tagName) && isOrphanEndTag();
}
/**
* Returns the offset at which the given unclosed start tag should be closed with an angle bracket
*
* @returns the offset at which the given unclosed start tag should be closed with an angle bracket
*/
public int getUnclosedStartTagCloseOffset() {
String documentText = getOwnerDocument().getText();
int i = getStart() + 1;
for (; i < documentText.length() && documentText.charAt(i) != '/' && documentText.charAt(i) != '<'; i++) {
}
if (i < documentText.length() && documentText.charAt(i) == '/') {
return i + 1;
}
i--;
for (; i > 0 && Character.isWhitespace(documentText.charAt(i)); i--) {
}
return i + 1;
}
@Override
public DOMElement getOrphanEndElement(int offset, String tagName) {
if (getEnd() <= offset) {
// <employee />|
// <employee /> |
// <employee></employee> |
// check if next sibling node is an element like <\tagName>
return super.getOrphanEndElement(offset, tagName);
}
if (isSameTag(tagName) && isInStartTag(offset)) {
// <employe|e></employee>
return hasEndTag() ? this : null;
}
// search if it exists an end tag
List<DOMNode> children = getChildren();
for (DOMNode child : children) {
if (child.isElement()) {
DOMElement childElement = (DOMElement) child;
if (childElement.isOrphanEndTagOf(tagName)) {
return childElement;
}
}
}
return null;
}
/**
* Returns true if element has a closing end tag (eg: <a> </a>) and false
* otherwise (eg: <a> </b>).
*
* @return true if element has a closing end tag (eg: <a> </a>) and false
* otherwise (eg: <a> </b>).
*/
@Override
public boolean isClosed() {
return super.isClosed();
}
@Override
public String getAttributeNS(String arg0, String arg1) throws DOMException {
return null;
}
@Override
public DOMAttr getAttributeNode(String name) {
return super.getAttributeNode(name);
}
@Override
public DOMAttr getAttributeNodeNS(String arg0, String arg1) throws DOMException {
return null;
}
@Override
public NodeList getElementsByTagName(String arg0) {
return null;
}
@Override
public NodeList getElementsByTagNameNS(String arg0, String arg1) throws DOMException {
return null;
}
@Override
public TypeInfo getSchemaTypeInfo() {
return null;
}
@Override
public boolean hasAttributeNS(String arg0, String arg1) throws DOMException {
return false;
}
@Override
public void removeAttribute(String arg0) throws DOMException {
}
@Override
public void removeAttributeNS(String arg0, String arg1) throws DOMException {
}
@Override
public DOMAttr removeAttributeNode(org.w3c.dom.Attr arg0) throws DOMException {
return null;
}
@Override
public void setAttributeNS(String arg0, String arg1, String arg2) throws DOMException {
}
@Override
public DOMAttr setAttributeNode(org.w3c.dom.Attr arg0) throws DOMException {
return null;
}
@Override
public DOMAttr setAttributeNodeNS(org.w3c.dom.Attr arg0) throws DOMException {
return null;
}
@Override
public void setIdAttribute(String arg0, boolean arg1) throws DOMException {
}
@Override
public void setIdAttributeNS(String arg0, String arg1, boolean arg2) throws DOMException {
}
@Override
public void setIdAttributeNode(org.w3c.dom.Attr arg0, boolean arg1) throws DOMException {
}
/**
* Returns true if the element is empty and false otherwise.
*
* @return true if the element is empty and false otherwise.
*/
public boolean isEmpty() {
if (!hasChildNodes()) {
return true;
}
for (DOMNode child : getChildren()) {
if (child.isText()) {
DOMText text = (DOMText) child;
if (!text.isWhitespace()) {
return false;
}
} else {
return false;
}
}
return true;
}
}
@@ -1,846 +0,0 @@
/**
* Copyright (c) 2018 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
* http://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
*/
package org.eclipse.lemminx.dom;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import org.w3c.dom.DOMException;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.UserDataHandler;
/**
* DOM node.
*
*/
public abstract class DOMNode implements Node, DOMRange {
/**
* Null value used for offset.
*/
public static final int NULL_VALUE = -1;
/**
* The node is a <code>DTD Element Declaration</code>.
*/
public static final short DTD_ELEMENT_DECL_NODE = 101;
/**
* The node is a <code>DTD Attribute List</code>.
*/
public static final short DTD_ATT_LIST_NODE = 102;
/**
* The node is a <code>DTD Entity Declaraction</code>.
*/
public static final short DTD_ENTITY_DECL_NODE = 103;
/**
* The node is a <code>DTD Notation Declaraction</code>.
*/
public static final short DTD_NOTATION_DECL = 104;
/**
* The node is a generic <code>DTD Decl Node</code>.
*/
public static final short DTD_DECL_NODE = 105;
boolean closed = false;
private XMLNamedNodeMap<DOMAttr> attributeNodes;
private XMLNodeList<DOMNode> children;
final int start; // |<root> </root>
int end; // <root> </root>|
DOMNode parent;
private static final NodeList EMPTY_CHILDREN = new NodeList() {
@Override
public Node item(int index) {
return null;
}
@Override
public int getLength() {
return 0;
}
};
static class XMLNodeList<T extends DOMNode> extends ArrayList<T> implements NodeList {
private static final long serialVersionUID = 1L;
@Override
public int getLength() {
return super.size();
}
@Override
public DOMNode item(int index) {
return super.get(index);
}
}
static class XMLNamedNodeMap<T extends DOMNode> extends ArrayList<T> implements NamedNodeMap {
private static final long serialVersionUID = 1L;
@Override
public int getLength() {
return super.size();
}
@Override
public T getNamedItem(String name) {
for (T node : this) {
if (name.equals(node.getNodeName())) {
return node;
}
}
return null;
}
@Override
public T getNamedItemNS(String name, String arg1) throws DOMException {
throw new UnsupportedOperationException();
}
@Override
public T item(int index) {
return super.get(index);
}
@Override
public T removeNamedItem(String arg0) throws DOMException {
throw new UnsupportedOperationException();
}
@Override
public T removeNamedItemNS(String arg0, String arg1) throws DOMException {
throw new UnsupportedOperationException();
}
@Override
public T setNamedItem(org.w3c.dom.Node arg0) throws DOMException {
throw new UnsupportedOperationException();
}
@Override
public T setNamedItemNS(org.w3c.dom.Node arg0) throws DOMException {
throw new UnsupportedOperationException();
}
}
public DOMNode(int start, int end) {
this.start = start;
this.end = end;
this.closed = false;
}
/**
* Returns the owner document and null otherwise.
*
* @return the owner document and null otherwise.
*/
@Override
public DOMDocument getOwnerDocument() {
Node node = parent;
while (node != null) {
if (node.getNodeType() == Node.DOCUMENT_NODE) {
return (DOMDocument) node;
}
node = node.getParentNode();
}
return null;
}
@Override
public String toString() {
return toString(0);
}
private String toString(int indent) {
StringBuilder result = new StringBuilder("");
for (int i = 0; i < indent; i++) {
result.append("\t");
}
result.append("{start: ");
result.append(start);
result.append(", end: ");
result.append(end);
result.append(", name: ");
result.append(getNodeName());
result.append(", closed: ");
result.append(closed);
if (children != null && children.size() > 0) {
result.append(", \n");
for (int i = 0; i < indent + 1; i++) {
result.append("\t");
}
result.append("children:[");
for (int i = 0; i < children.size(); i++) {
DOMNode node = children.get(i);
result.append("\n");
result.append(node.toString(indent + 2));
if (i < children.size() - 1) {
result.append(",");
}
}
result.append("\n");
for (int i = 0; i < indent + 1; i++) {
result.append("\t");
}
result.append("]");
result.append("\n");
for (int i = 0; i < indent; i++) {
result.append("\t");
}
result.append("}");
} else {
result.append("}");
}
return result.toString();
}
/**
* Returns the node before
*/
public DOMNode findNodeBefore(int offset) {
List<DOMNode> children = getChildren();
int idx = findFirst(children, c -> offset <= c.start) - 1;
if (idx >= 0) {
DOMNode child = children.get(idx);
if (offset > child.start) {
if (offset < child.end) {
return child.findNodeBefore(offset);
}
DOMNode lastChild = child.getLastChild();
if (lastChild != null && lastChild.end == child.end) {
return child.findNodeBefore(offset);
}
return child;
}
}
return this;
}
public DOMNode findNodeAt(int offset) {
List<DOMNode> children = getChildren();
int idx = findFirst(children, c -> offset <= c.start) - 1;
if (idx >= 0) {
DOMNode child = children.get(idx);
if (isIncluded(child, offset)) {
return child.findNodeAt(offset);
}
}
return this;
}
/**
* Returns true if the node included the given offset and false otherwise.
*
* @param node
* @param offset
* @return true if the node included the given offset and false otherwise.
*/
public static boolean isIncluded(DOMRange node, int offset) {
if (node == null) {
return false;
}
return isIncluded(node.getStart(), node.getEnd(), offset);
}
public static boolean isIncluded(int start, int end, int offset) {
return offset >= start && offset <= end;
}
public DOMAttr findAttrAt(int offset) {
DOMNode node = findNodeAt(offset);
return findAttrAt(node, offset);
}
public static DOMAttr findAttrAt(DOMNode node, int offset) {
if (node != null && node.hasAttributes()) {
for (DOMAttr attr : node.getAttributeNodes()) {
if (attr.isIncluded(offset)) {
return attr;
}
}
}
return null;
}
public static DOMNode findNodeOrAttrAt(DOMDocument document, int offset) {
DOMNode node = document.findNodeAt(offset);
if (node != null) {
DOMAttr attr = findAttrAt(node, offset);
if (attr != null) {
return attr;
}
}
return node;
}
/**
* Takes a sorted array and a function p. The array is sorted in such a way that
* all elements where p(x) is false are located before all elements where p(x)
* is true.
*
* @returns the least x for which p(x) is true or array.length if no element
* full fills the given function.
*/
private static <T> int findFirst(List<T> array, Function<T, Boolean> p) {
int low = 0, high = array.size();
if (high == 0) {
return 0; // no children
}
while (low < high) {
int mid = (int) Math.floor((low + high) / 2);
if (p.apply(array.get(mid))) {
high = mid;
} else {
low = mid + 1;
}
}
return low;
}
public DOMAttr getAttributeNode(String name) {
return getAttributeNode(null, name);
}
/**
* Returns the attribute that matches the given name.
*
* If there is no namespace, set prefix to null.
*/
public DOMAttr getAttributeNode(String prefix, String suffix) {
StringBuilder sb = new StringBuilder();
if (prefix != null) {
sb.append(prefix);
sb.append(":");
}
sb.append(suffix);
String name = sb.toString();
if (!hasAttributes()) {
return null;
}
for (DOMAttr attr : attributeNodes) {
if (name.equals(attr.getName())) {
return attr;
}
}
return null;
}
public String getAttribute(String name) {
DOMAttr attr = getAttributeNode(name);
String value = attr != null ? attr.getValue() : null;
if (value == null) {
return null;
}
if (value.isEmpty()) {
return value;
}
// remove quote
char c = value.charAt(0);
if (c == '"' || c == '\'') {
if (value.charAt(value.length() - 1) == c) {
return value.substring(1, value.length() - 1);
}
return value.substring(1, value.length());
}
return value;
}
/**
* Returns the attribute at the given index, the order is how the attributes
* appear in the document.
*
* @param index Starting at 0, index of attribute you want
* @return
*/
public DOMAttr getAttributeAtIndex(int index) {
if (!hasAttributes()) {
return null;
}
if (index > attributeNodes.getLength() - 1) {
return null;
}
return attributeNodes.get(index);
}
public boolean hasAttribute(String name) {
return hasAttributes() && getAttributeNode(name) != null;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#hasAttributes()
*/
@Override
public boolean hasAttributes() {
return attributeNodes != null && attributeNodes.size() != 0;
}
public void setAttribute(String name, String value) {
DOMAttr attr = getAttributeNode(name);
if (attr == null) {
attr = new DOMAttr(name, this);
setAttributeNode(attr);
}
attr.setValue(value, -1, -1);
}
public void setAttributeNode(DOMAttr attr) {
if (attributeNodes == null) {
attributeNodes = new XMLNamedNodeMap<>();
}
attributeNodes.add(attr);
}
public List<DOMAttr> getAttributeNodes() {
return attributeNodes;
}
/**
* Returns a list of children, each having an attribute called name, with a
* value of value
*
* @param name name of attribute
* @param value value of attribute
* @return list of children, each having a specified attribute name and value
*/
public List<DOMNode> getChildrenWithAttributeValue(String name, String value) {
List<DOMNode> result = new ArrayList<>();
for (DOMNode child : getChildren()) {
if (child.hasAttribute(name)) {
String attrValue = child.getAttribute(name);
if (Objects.equals(attrValue, value)) {
result.add(child);
}
}
}
return result;
}
/**
* Returns the node children.
*
* @return the node children.
*/
public List<DOMNode> getChildren() {
if (children == null) {
return Collections.emptyList();
}
return children;
}
/**
* Add node child and set child.parent to {@code this}
*
* @param child the node child to add.
*/
public void addChild(DOMNode child) {
child.parent = this;
if (children == null) {
children = new XMLNodeList<>();
}
getChildren().add(child);
}
/**
* Returns node child at the given index.
*
* @param index
* @return node child at the given index.
*/
public DOMNode getChild(int index) {
return getChildren().get(index);
}
public boolean isClosed() {
return closed;
}
public DOMElement getParentElement() {
DOMNode parent = getParentNode();
DOMDocument ownerDocument = getOwnerDocument();
while (parent != null && parent != ownerDocument) {
if (parent.isElement()) {
return (DOMElement) parent;
}
parent = parent.getParentNode();
}
return null;
}
public boolean isComment() {
return getNodeType() == DOMNode.COMMENT_NODE;
}
public boolean isProcessingInstruction() {
return (getNodeType() == DOMNode.PROCESSING_INSTRUCTION_NODE)
&& ((DOMProcessingInstruction) this).isProcessingInstruction();
}
public boolean isProlog() {
return (getNodeType() == DOMNode.PROCESSING_INSTRUCTION_NODE) && ((DOMProcessingInstruction) this).isProlog();
}
public boolean isCDATA() {
return getNodeType() == DOMNode.CDATA_SECTION_NODE;
}
public boolean isDoctype() {
return getNodeType() == DOMNode.DOCUMENT_TYPE_NODE;
}
public boolean isGenericDTDDecl() {
return getNodeType() == DOMNode.DTD_DECL_NODE;
}
public boolean isElement() {
return getNodeType() == DOMNode.ELEMENT_NODE;
}
public boolean isAttribute() {
return getNodeType() == DOMNode.ATTRIBUTE_NODE;
}
public boolean isText() {
return getNodeType() == DOMNode.TEXT_NODE;
}
public boolean isCharacterData() {
return isCDATA() || isText() || isProcessingInstruction() || isComment();
}
public boolean isDTDElementDecl() {
return getNodeType() == DOMNode.DTD_ELEMENT_DECL_NODE;
}
public boolean isDTDAttListDecl() {
return getNodeType() == DOMNode.DTD_ATT_LIST_NODE;
}
public boolean isDTDEntityDecl() {
return getNodeType() == Node.ENTITY_NODE;
}
public boolean isDTDNotationDecl() {
return getNodeType() == DOMNode.DTD_NOTATION_DECL;
}
public boolean isOwnerDocument() {
return getNodeType() == Node.DOCUMENT_NODE;
}
public boolean isChildOfOwnerDocument() {
if (parent == null) {
return false;
}
return parent.getNodeType() == Node.DOCUMENT_NODE;
}
@Override
public int getStart() {
return start;
}
@Override
public int getEnd() {
return end;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getLocalName()
*/
@Override
public String getLocalName() {
return null;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getParentNode()
*/
@Override
public DOMNode getParentNode() {
return parent;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getFirstChild()
*/
@Override
public DOMNode getFirstChild() {
return this.children != null && children.size() > 0 ? this.children.get(0) : null;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getLastChild()
*/
@Override
public DOMNode getLastChild() {
return this.children != null && this.children.size() > 0 ? this.children.get(this.children.size() - 1) : null;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getAttributes()
*/
@Override
public NamedNodeMap getAttributes() {
return attributeNodes;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getChildNodes()
*/
@Override
public NodeList getChildNodes() {
return children != null ? children : EMPTY_CHILDREN;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#appendChild(org.w3c.dom.Node)
*/
@Override
public org.w3c.dom.Node appendChild(org.w3c.dom.Node newChild) throws DOMException {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#cloneNode(boolean)
*/
@Override
public org.w3c.dom.Node cloneNode(boolean deep) {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#compareDocumentPosition(org.w3c.dom.Node)
*/
@Override
public short compareDocumentPosition(org.w3c.dom.Node other) throws DOMException {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getBaseURI()
*/
@Override
public String getBaseURI() {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getFeature(java.lang.String, java.lang.String)
*/
@Override
public Object getFeature(String arg0, String arg1) {
throw new UnsupportedOperationException();
}
@Override
public String getNamespaceURI() {
return null;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getNextSibling()
*/
@Override
public DOMNode getNextSibling() {
DOMNode parentNode = getParentNode();
if (parentNode == null) {
return null;
}
List<DOMNode> children = parentNode.getChildren();
int nextIndex = children.indexOf(this) + 1;
return nextIndex < children.size() ? children.get(nextIndex) : null;
}
@Override
public String getNodeValue() throws DOMException {
return null;
}
@Override
public String getPrefix() {
return null;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getPreviousSibling()
*/
@Override
public DOMNode getPreviousSibling() {
DOMNode parentNode = getParentNode();
if (parentNode == null) {
return null;
}
List<DOMNode> children = parentNode.getChildren();
int previousIndex = children.indexOf(this) - 1;
return previousIndex >= 0 ? children.get(previousIndex) : null;
}
public DOMNode getPreviousNonTextSibling() {
DOMNode prev = getPreviousSibling();
while (prev != null && prev.isText()) {
prev = prev.getPreviousSibling();
}
return prev;
}
public DOMElement getOrphanEndElement(int offset, String tagName) {
DOMNode next = getNextSibling();
if (next == null || !next.isElement()) {
return null;
}
// emp| </employe>
DOMElement nextElement = (DOMElement) next;
if (nextElement.isOrphanEndTagOf(tagName)) {
return nextElement;
}
return null;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getTextContent()
*/
@Override
public String getTextContent() throws DOMException {
return getNodeValue();
}
@Override
public Object getUserData(String arg0) {
return null;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#hasChildNodes()
*/
@Override
public boolean hasChildNodes() {
return children != null && !children.isEmpty();
}
@Override
public org.w3c.dom.Node insertBefore(org.w3c.dom.Node arg0, org.w3c.dom.Node arg1) throws DOMException {
return null;
}
@Override
public boolean isDefaultNamespace(String arg0) {
return false;
}
@Override
public boolean isEqualNode(org.w3c.dom.Node arg0) {
return false;
}
@Override
public boolean isSameNode(org.w3c.dom.Node arg0) {
return false;
}
@Override
public boolean isSupported(String arg0, String arg1) {
return false;
}
@Override
public String lookupNamespaceURI(String arg0) {
return null;
}
@Override
public String lookupPrefix(String arg0) {
return null;
}
@Override
public void normalize() {
}
@Override
public org.w3c.dom.Node removeChild(org.w3c.dom.Node arg0) throws DOMException {
return null;
}
@Override
public org.w3c.dom.Node replaceChild(org.w3c.dom.Node arg0, org.w3c.dom.Node arg1) throws DOMException {
return null;
}
@Override
public void setNodeValue(String arg0) throws DOMException {
}
@Override
public void setPrefix(String arg0) throws DOMException {
}
@Override
public void setTextContent(String arg0) throws DOMException {
}
@Override
public Object setUserData(String arg0, Object arg1, UserDataHandler arg2) {
return null;
}
}
@@ -1,683 +0,0 @@
/**
* Copyright (c) 2018 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
* http://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Initial code from https://github.com/Microsoft/vscode-html-languageservice
* Initial copyright Copyright (C) Microsoft Corporation. All rights reserved.
* Initial license: MIT
*
* Contributors:
* - Microsoft Corporation: Initial code, written in TypeScript, licensed under MIT license
* - Angelo Zerr <angelo.zerr@gmail.com> - translation and adaptation to Java
*/
package org.eclipse.lemminx.dom;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.eclipse.lemminx.commons.BadLocationException;
import org.eclipse.lemminx.commons.TextDocument;
import org.eclipse.lemminx.dom.parser.Scanner;
import org.eclipse.lemminx.dom.parser.TokenType;
import org.eclipse.lemminx.dom.parser.XMLScanner;
import org.eclipse.lemminx.uriresolver.URIResolverExtensionManager;
import org.eclipse.lemminx.utils.DOMUtils;
import org.eclipse.lemminx.utils.StringUtils;
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
/**
* Tolerant XML parser.
*
*/
public class DOMParser {
private static final Logger LOGGER = Logger.getLogger(DOMParser.class.getName());
private static final DOMParser INSTANCE = new DOMParser();
public static DOMParser getInstance() {
return INSTANCE;
}
private DOMParser() {
}
public DOMDocument parse(String text, String uri, URIResolverExtensionManager resolverExtensionManager) {
return parse(new TextDocument(text, uri), resolverExtensionManager);
}
public DOMDocument parse(String text, String uri, URIResolverExtensionManager resolverExtensionManager,
boolean ignoreWhitespaceContent) {
return parse(new TextDocument(text, uri), resolverExtensionManager, ignoreWhitespaceContent);
}
public DOMDocument parse(TextDocument document, URIResolverExtensionManager resolverExtensionManager) {
return parse(document, resolverExtensionManager, true);
}
public DOMDocument parse(TextDocument document, URIResolverExtensionManager resolverExtensionManager,
boolean ignoreWhitespaceContent) {
return parse(document, resolverExtensionManager, ignoreWhitespaceContent, null);
}
public DOMDocument parse(TextDocument document, URIResolverExtensionManager resolverExtensionManager,
boolean ignoreWhitespaceContent, CancelChecker monitor) {
boolean isDTD = DOMUtils.isDTD(document.getUri());
boolean inDTDInternalSubset = false;
String text = document.getText();
Scanner scanner = XMLScanner.createScanner(text, 0, isDTD);
DOMDocument xmlDocument = new DOMDocument(document, resolverExtensionManager);
xmlDocument.setCancelChecker(monitor);
DOMNode curr = isDTD ? new DOMDocumentType(0, text.length()) : xmlDocument;
if (isDTD) {
xmlDocument.addChild(curr);
// This DOMDocumentType object is hidden, and just represents the DTD file
// nothing should affect it's closed status
curr.closed = true;
}
DOMNode lastClosed = curr;
DOMAttr attr = null;
int endTagOpenOffset = -1;
String pendingAttribute = null;
DOMNode tempWhitespaceContent = null;
boolean isInitialDeclaration = true; // A declaration can have multiple internal declarations
boolean previousTokenWasEndTagOpen = false;
TokenType token = scanner.scan();
while (token != TokenType.EOS) {
if (monitor != null) {
monitor.checkCanceled();
}
if (tempWhitespaceContent != null && token != TokenType.EndTagOpen) {
tempWhitespaceContent = null;
}
if (previousTokenWasEndTagOpen) {
previousTokenWasEndTagOpen = false;
if (token != TokenType.EndTag) {
// The excepted token is not an EndTag, create a fake end tag element
DOMElement element = xmlDocument.createElement(endTagOpenOffset, endTagOpenOffset + 2);
element.endTagOpenOffset = endTagOpenOffset;
curr.addChild(element);
}
}
switch (token) {
case StartTagOpen: {
if (!curr.isClosed() && curr.parent != null) {
// The next node's parent (curr) is not closed at this point
// so the node's parent (curr) will have its end position updated
// to a newer end position.
curr.end = scanner.getTokenOffset();
}
if ((curr.isClosed()) || curr.isDoctype()) {
// The next node being considered is a child of 'curr'
// and if 'curr' is already closed then 'curr' was not updated properly.
// Or if we get a Doctype node then we know it was not closed and 'curr'
// wasn't updated properly.
curr = curr.parent;
inDTDInternalSubset = false; // In case it was previously in the internal subset
}
DOMElement child = xmlDocument.createElement(scanner.getTokenOffset(), scanner.getTokenEnd());
child.startTagOpenOffset = scanner.getTokenOffset();
curr.addChild(child);
curr = child;
break;
}
case StartTag: {
DOMElement element = (DOMElement) curr;
element.tag = scanner.getTokenText();
curr.end = scanner.getTokenEnd();
break;
}
case StartTagClose:
if (curr.isElement()) {
DOMElement element = (DOMElement) curr;
curr.end = scanner.getTokenEnd(); // might be later set to end tag position
element.startTagCloseOffset = scanner.getTokenOffset();
// never enters isEmptyElement() is always false
if (element.hasTagName() && isEmptyElement(element.getTagName()) && curr.parent != null) {
curr.closed = true;
curr = curr.parent;
}
} else if (curr.isProcessingInstruction() || curr.isProlog()) {
DOMProcessingInstruction element = (DOMProcessingInstruction) curr;
curr.end = scanner.getTokenEnd(); // might be later set to end tag position
element.startTagClose = true;
if (element.getTarget() != null && isEmptyElement(element.getTarget()) && curr.parent != null) {
curr.closed = true;
curr = curr.parent;
}
}
curr.end = scanner.getTokenEnd();
break;
case EndTagOpen:
if (tempWhitespaceContent != null) {
curr.addChild(tempWhitespaceContent);
tempWhitespaceContent = null;
}
endTagOpenOffset = scanner.getTokenOffset();
curr.end = scanner.getTokenOffset();
previousTokenWasEndTagOpen = true;
break;
case EndTag:
// end tag (ex: </root>)
String closeTag = scanner.getTokenText();
DOMNode current = curr;
/**
* eg: <a><b><c></d> will set a,b,c end position to the start of |</d>
*/
while (!(curr.isElement() && ((DOMElement) curr).isSameTag(closeTag)) && curr.parent != null) {
curr.end = endTagOpenOffset;
curr = curr.parent;
}
if (curr != xmlDocument) {
curr.closed = true;
if (curr.isElement()) {
((DOMElement) curr).endTagOpenOffset = endTagOpenOffset;
} else if (curr.isProcessingInstruction() || curr.isProlog()) {
((DOMProcessingInstruction) curr).endTagOpenOffset = endTagOpenOffset;
}
curr.end = scanner.getTokenEnd();
} else {
// element open tag not found (ex: <root>) add a fake element which only has an
// end tag (no start tag).
DOMElement element = xmlDocument.createElement(scanner.getTokenOffset() - 2, scanner.getTokenEnd());
element.endTagOpenOffset = endTagOpenOffset;
element.tag = closeTag;
current.addChild(element);
curr = element;
}
break;
case StartTagSelfClose:
if (curr.parent != null) {
curr.closed = true;
((DOMElement) curr).selfClosed = true;
curr.end = scanner.getTokenEnd();
lastClosed = curr;
curr = curr.parent;
}
break;
case EndTagClose:
if (curr.parent != null) {
curr.end = scanner.getTokenEnd();
lastClosed = curr;
if (lastClosed.isElement()) {
((DOMElement) curr).endTagCloseOffset = scanner.getTokenOffset();
}
if (curr.isDoctype()) {
curr.closed = true;
}
curr = curr.parent;
}
break;
case AttributeName: {
pendingAttribute = scanner.getTokenText();
attr = new DOMAttr(pendingAttribute, scanner.getTokenOffset(),
scanner.getTokenOffset() + pendingAttribute.length(), curr);
curr.setAttributeNode(attr);
curr.end = scanner.getTokenEnd();
break;
}
case DelimiterAssign: {
if (attr != null) {
// Sets the value to the '=' position in case there is no AttributeValue
attr.setValue(null, scanner.getTokenOffset(), scanner.getTokenEnd());
attr.setDelimiter(true);
}
break;
}
case AttributeValue: {
String value = scanner.getTokenText();
if (curr.hasAttributes() && attr != null) {
attr.setValue(value, scanner.getTokenOffset(), scanner.getTokenOffset() + value.length());
}
pendingAttribute = null;
attr = null;
curr.end = scanner.getTokenEnd();
break;
}
case CDATATagOpen: {
DOMCDATASection cdataNode = xmlDocument.createCDataSection(scanner.getTokenOffset(), text.length());
curr.addChild(cdataNode);
curr = cdataNode;
break;
}
case CDATAContent: {
DOMCDATASection cdataNode = (DOMCDATASection) curr;
cdataNode.startContent = scanner.getTokenOffset();
cdataNode.endContent = scanner.getTokenEnd();
curr.end = scanner.getTokenEnd();
break;
}
case CDATATagClose: {
curr.end = scanner.getTokenEnd();
curr.closed = true;
curr = curr.parent;
break;
}
case StartPrologOrPI: {
DOMProcessingInstruction prologOrPINode = xmlDocument
.createProcessingInstruction(scanner.getTokenOffset(), text.length());
curr.addChild(prologOrPINode);
curr = prologOrPINode;
break;
}
case PIName: {
DOMProcessingInstruction processingInstruction = ((DOMProcessingInstruction) curr);
processingInstruction.target = scanner.getTokenText();
processingInstruction.processingInstruction = true;
break;
}
case PrologName: {
DOMProcessingInstruction processingInstruction = ((DOMProcessingInstruction) curr);
processingInstruction.target = scanner.getTokenText();
processingInstruction.prolog = true;
break;
}
case PIContent: {
DOMProcessingInstruction processingInstruction = (DOMProcessingInstruction) curr;
processingInstruction.startContent = scanner.getTokenOffset();
processingInstruction.endContent = scanner.getTokenEnd();
break;
}
case PIEnd:
case PrologEnd: {
curr.end = scanner.getTokenEnd();
curr.closed = true;
curr = curr.parent;
break;
}
case StartCommentTag: {
// Incase the tag before the comment tag (curr) was not properly closed
// curr should be set to the root node.
if (xmlDocument.isDTD() || inDTDInternalSubset) {
while (!curr.isDoctype()) {
curr = curr.parent;
}
} else if ((curr.isClosed())) {
curr = curr.parent;
}
DOMComment comment = xmlDocument.createComment(scanner.getTokenOffset(), text.length());
curr.addChild(comment);
curr = comment;
try {
int endLine = document.positionAt(lastClosed.end).getLine();
int startLine = document.positionAt(curr.start).getLine();
if (endLine == startLine && lastClosed.end <= curr.start) {
comment.commentSameLineEndTag = true;
}
} catch (BadLocationException e) {
LOGGER.log(Level.SEVERE, "XMLParser StartCommentTag bad offset in document", e);
}
break;
}
case Comment: {
DOMComment comment = (DOMComment) curr;
comment.startContent = scanner.getTokenOffset();
comment.endContent = scanner.getTokenEnd();
break;
}
case EndCommentTag: {
curr.end = scanner.getTokenEnd();
curr.closed = true;
curr = curr.parent;
break;
}
case Content: {
boolean currIsDeclNode = curr instanceof DTDDeclNode;
if (currIsDeclNode) {
curr.end = scanner.getTokenOffset() - 1;
while (!curr.isDoctype()) {
curr = curr.getParentNode();
}
}
int start = scanner.getTokenOffset();
int end = scanner.getTokenEnd();
DOMText textNode = xmlDocument.createText(start, end);
textNode.closed = true;
if (scanner.isTokenTextBlank()) {
if (ignoreWhitespaceContent) {
if (curr.hasChildNodes()) {
break;
}
tempWhitespaceContent = textNode;
break;
} else if (!currIsDeclNode) {
textNode.setWhitespace(true);
} else {
break;
}
}
curr.addChild(textNode);
break;
}
// DTD
case DTDStartDoctypeTag: {
DOMDocumentType doctype = xmlDocument.createDocumentType(scanner.getTokenOffset(), text.length());
curr.addChild(doctype);
doctype.parent = curr;
curr = doctype;
break;
}
case DTDDoctypeName: {
DOMDocumentType doctype = (DOMDocumentType) curr;
doctype.setName(scanner.getTokenOffset(), scanner.getTokenEnd());
break;
}
case DTDDocTypeKindPUBLIC: {
DOMDocumentType doctype = (DOMDocumentType) curr;
doctype.setKind(scanner.getTokenOffset(), scanner.getTokenEnd());
break;
}
case DTDDocTypeKindSYSTEM: {
DOMDocumentType doctype = (DOMDocumentType) curr;
doctype.setKind(scanner.getTokenOffset(), scanner.getTokenEnd());
break;
}
case DTDDoctypePublicId: {
DOMDocumentType doctype = (DOMDocumentType) curr;
doctype.setPublicId(scanner.getTokenOffset(), scanner.getTokenEnd());
break;
}
case DTDDoctypeSystemId: {
DOMDocumentType doctype = (DOMDocumentType) curr;
doctype.setSystemId(scanner.getTokenOffset(), scanner.getTokenEnd());
break;
}
case DTDStartInternalSubset: {
DOMDocumentType doctype = (DOMDocumentType) curr;
doctype.setStartInternalSubset(scanner.getTokenOffset());
inDTDInternalSubset = true;
break;
}
case DTDEndInternalSubset: {
while (!curr.isDoctype()) {
curr.end = scanner.getTokenOffset() - 1;
curr = curr.getParentNode();
}
inDTDInternalSubset = false;
DOMDocumentType doctype = (DOMDocumentType) curr;
doctype.setEndInternalSubset(scanner.getTokenEnd());
break;
}
case DTDStartElement: {
// If previous 'curr' was an unclosed DTD Declaration
while (!curr.isDoctype()) {
curr.end = scanner.getTokenOffset();
curr = curr.getParentNode();
}
DTDElementDecl child = new DTDElementDecl(scanner.getTokenOffset(), text.length());
curr.addChild(child);
curr = child;
break;
}
case DTDElementDeclName: {
DTDElementDecl element = (DTDElementDecl) curr;
element.setName(scanner.getTokenOffset(), scanner.getTokenEnd());
break;
}
case DTDElementCategory: {
DTDElementDecl element = (DTDElementDecl) curr;
element.setCategory(scanner.getTokenOffset(), scanner.getTokenEnd());
break;
}
case DTDStartElementContent: {
DTDElementDecl element = (DTDElementDecl) curr;
element.setContent(scanner.getTokenOffset(), scanner.getTokenEnd());
break;
}
case DTDElementContent: {
DTDElementDecl element = (DTDElementDecl) curr;
element.updateLastParameterEnd(scanner.getTokenEnd());
break;
}
case DTDEndElementContent: {
DTDElementDecl element = (DTDElementDecl) curr;
element.updateLastParameterEnd(scanner.getTokenEnd());
break;
}
case DTDStartAttlist: {
while (!curr.isDoctype()) { // If previous DTD Decl was unclosed
curr.end = scanner.getTokenOffset();
curr = curr.getParentNode();
}
DTDAttlistDecl child = new DTDAttlistDecl(scanner.getTokenOffset(), text.length());
isInitialDeclaration = true;
curr.addChild(child);
curr = child;
break;
}
case DTDAttlistElementName: {
DTDAttlistDecl attribute = (DTDAttlistDecl) curr;
attribute.setName(scanner.getTokenOffset(), scanner.getTokenEnd());
break;
}
case DTDAttlistAttributeName: {
DTDAttlistDecl attribute = (DTDAttlistDecl) curr;
if (isInitialDeclaration == false) {
// All additional declarations are created as new DTDAttlistDecl's
DTDAttlistDecl child = new DTDAttlistDecl(attribute.getStart(), attribute.getEnd());
attribute.addAdditionalAttDecl(child);
child.parent = attribute;
attribute = child;
curr = child;
}
attribute.setAttributeName(scanner.getTokenOffset(), scanner.getTokenEnd());
break;
}
case DTDAttlistAttributeType: {
DTDAttlistDecl attribute = (DTDAttlistDecl) curr;
attribute.setAttributeType(scanner.getTokenOffset(), scanner.getTokenEnd());
break;
}
case DTDAttlistAttributeValue: {
DTDAttlistDecl attribute = (DTDAttlistDecl) curr;
attribute.setAttributeValue(scanner.getTokenOffset(), scanner.getTokenEnd());
if (attribute.parent.isDTDAttListDecl()) { // Is not the root/main ATTLIST node
curr = attribute.parent;
} else {
isInitialDeclaration = false;
}
break;
}
case DTDStartEntity: {
while (!curr.isDoctype()) { // If previous DTD Decl was unclosed
curr.end = scanner.getTokenOffset();
curr = curr.getParentNode();
}
DTDEntityDecl child = new DTDEntityDecl(scanner.getTokenOffset(), text.length());
curr.addChild(child);
curr = child;
break;
}
case DTDEntityPercent: {
DTDEntityDecl entity = (DTDEntityDecl) curr;
entity.setPercent(scanner.getTokenOffset(), scanner.getTokenEnd());
break;
}
case DTDEntityName: {
DTDEntityDecl entity = (DTDEntityDecl) curr;
entity.setName(scanner.getTokenOffset(), scanner.getTokenEnd());
break;
}
case DTDEntityValue: {
DTDEntityDecl entity = (DTDEntityDecl) curr;
entity.setValue(scanner.getTokenOffset(), scanner.getTokenEnd());
break;
}
case DTDEntityKindPUBLIC:
case DTDEntityKindSYSTEM: {
DTDEntityDecl entity = (DTDEntityDecl) curr;
entity.setKind(scanner.getTokenOffset(), scanner.getTokenEnd());
break;
}
case DTDEntityPublicId: {
DTDEntityDecl entity = (DTDEntityDecl) curr;
entity.setPublicId(scanner.getTokenOffset(), scanner.getTokenEnd());
break;
}
case DTDEntitySystemId: {
DTDEntityDecl entity = (DTDEntityDecl) curr;
entity.setSystemId(scanner.getTokenOffset(), scanner.getTokenEnd());
break;
}
case DTDStartNotation: {
while (!curr.isDoctype()) { // If previous DTD Decl was unclosed
curr.end = scanner.getTokenOffset();
curr = curr.getParentNode();
}
DTDNotationDecl child = new DTDNotationDecl(scanner.getTokenOffset(), text.length());
curr.addChild(child);
curr = child;
isInitialDeclaration = true;
break;
}
case DTDNotationName: {
DTDNotationDecl notation = (DTDNotationDecl) curr;
notation.setName(scanner.getTokenOffset(), scanner.getTokenEnd());
break;
}
case DTDNotationKindPUBLIC: {
DTDNotationDecl notation = (DTDNotationDecl) curr;
notation.setKind(scanner.getTokenOffset(), scanner.getTokenEnd());
break;
}
case DTDNotationKindSYSTEM: {
DTDNotationDecl notation = (DTDNotationDecl) curr;
notation.setKind(scanner.getTokenOffset(), scanner.getTokenEnd());
break;
}
case DTDNotationPublicId: {
DTDNotationDecl notation = (DTDNotationDecl) curr;
notation.setPublicId(scanner.getTokenOffset(), scanner.getTokenEnd());
break;
}
case DTDNotationSystemId: {
DTDNotationDecl notation = (DTDNotationDecl) curr;
notation.setSystemId(scanner.getTokenOffset(), scanner.getTokenEnd());
break;
}
case DTDEndTag: {
if ((curr.isDTDElementDecl() || curr.isDTDAttListDecl() || curr.isDTDEntityDecl()
|| curr.isDTDNotationDecl())) {
while (curr.parent != null && !curr.parent.isDoctype()) {
curr = curr.parent;
}
curr.end = scanner.getTokenEnd();
curr.closed = true;
curr = curr.parent;
}
break;
}
case DTDEndDoctypeTag: {
((DOMDocumentType) curr).end = scanner.getTokenEnd();
curr.closed = true;
curr = curr.parent;
break;
}
case DTDUnrecognizedParameters: {
DTDDeclNode node = (DTDDeclNode) curr;
node.setUnrecognized(scanner.getTokenOffset(), ((XMLScanner) scanner).getLastNonWhitespaceOffset());
break;
}
default:
}
token = scanner.scan();
}
if (previousTokenWasEndTagOpen) {
previousTokenWasEndTagOpen = false;
if (token != TokenType.EndTag) {
// The excepted token is not an EndTag, create a fake end tag element
DOMElement element = xmlDocument.createElement(endTagOpenOffset, endTagOpenOffset + 2);
element.endTagOpenOffset = endTagOpenOffset;
curr.addChild(element);
}
}
while (curr.parent != null) {
curr.end = text.length();
curr = curr.parent;
}
return xmlDocument;
}
private static boolean isEmptyElement(String tag) {
return false;
}
}
@@ -1,112 +0,0 @@
/**
* Copyright (c) 2018 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
* http://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
*/
package org.eclipse.lemminx.dom;
import org.w3c.dom.DOMException;
/**
* A processing instruction node.
*
*/
public class DOMProcessingInstruction extends DOMCharacterData implements org.w3c.dom.ProcessingInstruction {
boolean startTagClose;
String target;
boolean prolog = false;
boolean processingInstruction = false;
int startContent;
int endContent;
int endTagOpenOffset = NULL_VALUE;
public DOMProcessingInstruction(int start, int end) {
super(start, end);
}
public boolean isProlog() {
return prolog;
}
public boolean isProcessingInstruction() {
return processingInstruction;
}
public int getStartContent() {
return startContent;
}
public int getEndContent() {
return endContent;
}
/**
* Returns the end tag start offset and {@link DOMNode#NULL_VALUE} if it doesn't
* exist.
*
* @return the end tag start offset and {@link DOMNode#NULL_VALUE} if it doesn't
* exist.
*/
public int getEndTagStart() {
return endTagOpenOffset;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getNodeType()
*/
@Override
public short getNodeType() {
return DOMNode.PROCESSING_INSTRUCTION_NODE;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getNodeName()
*/
@Override
public String getNodeName() {
return getTarget();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.ProcessingInstruction#getTarget()
*/
@Override
public String getTarget() {
return target;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.ProcessingInstruction#getData()
*/
@Override
public String getData() {
return super.getData().trim();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.ProcessingInstruction#setData(java.lang.String)
*/
@Override
public void setData(String data) throws DOMException {
throw new UnsupportedOperationException();
}
}
@@ -1,42 +0,0 @@
/*******************************************************************************
* Copyright (c) 2019 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
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx.dom;
/**
* DOM range
*
* @author Angelo ZERR
*
*/
public interface DOMRange {
/**
* Returns the start offset of the node.
*
* @return the start offset of the node.
*/
int getStart();
/**
* Returns the end offset of the node.
*
* @return the end offset of the node.
*/
int getEnd();
/**
* Returns the owner document.
*
* @return the owner document.
*/
DOMDocument getOwnerDocument();
}
@@ -1,86 +0,0 @@
/**
* Copyright (c) 2018 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
* http://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
*/
package org.eclipse.lemminx.dom;
import org.w3c.dom.DOMException;
/**
* A Text node.
*
*/
public class DOMText extends DOMCharacterData implements org.w3c.dom.Text {
public DOMText(int start, int end) {
super(start, end);
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getNodeType()
*/
@Override
public short getNodeType() {
return DOMNode.TEXT_NODE;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getNodeName()
*/
@Override
public String getNodeName() {
return "#text";
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Text#getWholeText()
*/
@Override
public String getWholeText() {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Text#isElementContentWhitespace()
*/
@Override
public boolean isElementContentWhitespace() {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Text#replaceWholeText(java.lang.String)
*/
@Override
public DOMText replaceWholeText(String content) throws DOMException {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Text#splitText(int)
*/
@Override
public DOMText splitText(int offset) throws DOMException {
throw new UnsupportedOperationException();
}
}
@@ -1,130 +0,0 @@
/**
* Copyright (c) 2018 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
* http://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
*/
package org.eclipse.lemminx.dom;
import java.util.ArrayList;
import java.util.List;
/**
* DTD Attribute List declaration <!ATTLIST
*
* @see https://www.w3.org/TR/REC-xml/#attdecls
*
*/
public class DTDAttlistDecl extends DTDDeclNode {
/**
* Format:
*
* <!ATTLIST element-name attribute-name attribute-type "attribute-value>""
*
* or
*
* <pre>
* <!ATTLIST element-name
* attribute-name1 attribute-type1 "attribute-value1"
* attribute-name2 attribute-type2 "attribute-value2"
* ..
* </pre>
*
* >
*/
public DTDDeclParameter attributeName;
public DTDDeclParameter attributeType;
public DTDDeclParameter attributeValue;
private List<DTDAttlistDecl> internalChildren; // Holds all additional internal attlist declaractions
public DTDAttlistDecl(int start, int end) {
super(start, end);
setDeclType(start + 2, start + 9);
}
@Override
public String getNodeName() {
return getAttributeName();
}
/**
* Returns the element name
*
* @return the element name
*/
public String getElementName() {
return getName();
}
/**
* Returns the attribute name
*
* @return the attribute name
*/
public String getAttributeName() {
return attributeName != null ? attributeName.getParameter() : null;
}
public void setAttributeName(int start, int end) {
attributeName = addNewParameter(start, end);
}
public String getAttributeType() {
return attributeType != null ? attributeType.getParameter() : null;
}
public void setAttributeType(int start, int end) {
attributeType = addNewParameter(start, end);
}
public String getAttributeValue() {
return attributeValue != null ? attributeValue.getParameter() : null;
}
public void setAttributeValue(int start, int end) {
attributeValue = addNewParameter(start, end);
}
@Override
public short getNodeType() {
return DOMNode.DTD_ATT_LIST_NODE;
}
/**
* Add another internal attlist declaration to the list of children.
*
* An ATTLIST decl can internally declare multiple declarations, see top of
* file. This will add another one to its list of additional declarations.
*/
void addAdditionalAttDecl(DTDAttlistDecl child) {
if (internalChildren == null) {
internalChildren = new ArrayList<>();
}
internalChildren.add(child);
}
public List<DTDAttlistDecl> getInternalChildren() {
return internalChildren;
}
/**
* Returns true if this node's parent is the Doctype node.
*
*
* This is used because an Attlist declaration can have multiple attribute
* declarations within a tag that are each represented by this class.
*/
public boolean isRootAttlist() {
return this.parent.isDoctype();
}
}
@@ -1,155 +0,0 @@
/*******************************************************************************
* Copyright (c) 2018 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.dom;
import java.util.ArrayList;
import java.util.List;
import org.w3c.dom.Node;
/**
* DTDNode
*/
public class DTDDeclNode extends DOMNode {
/**
* This class is the base for all declaration nodes for DTD's.
*
* It can also be used to represent an undefined tag, meaning it is not any of:
* ELEMENT, ATTLIST, ENTITY, or NOTATION
*/
public DTDDeclParameter unrecognized; // holds all content after parsing goes wrong in a DTD declaration (ENTITY,
// ATTLIST, ...).
public DTDDeclParameter declType; // represents the actual name of the decl eg: ENTITY, ATTLIST, ...
private List<DTDDeclParameter> parameters;
private DTDDeclParameter name;
public DTDDeclNode(int start, int end) {
super(start, end);
}
public String getName() {
DTDDeclParameter name = getNameParameter();
return name != null ? name.getParameter() : null;
}
public DTDDeclParameter getNameParameter() {
return name;
}
protected DTDDeclParameter getParameterAtIndex(int index) {
return parameters != null && parameters.size() > index ? parameters.get(index) : null;
}
public void setName(int start, int end) {
name = addNewParameter(start, end);
}
public boolean isInNameParameter(int offset) {
DTDDeclParameter name = getNameParameter();
return DOMNode.isIncluded(name, offset);
}
public DOMDocumentType getOwnerDocType() {
Node node = parent;
while (node != null) {
if (node.getNodeType() == Node.DOCUMENT_TYPE_NODE) {
return (DOMDocumentType) node;
}
node = node.getParentNode();
}
return null;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Node#getNodeName()
*/
@Override
public String getNodeName() {
return getName();
}
@Override
public short getNodeType() {
return DOMNode.DTD_DECL_NODE;
}
public String getUnrecognized() {
return unrecognized.getParameter();
}
public void setUnrecognized(int start, int end) {
unrecognized = addNewParameter(start, end);
}
public DTDDeclParameter addNewParameter(int start, int end) {
if (parameters == null) {
parameters = new ArrayList<>();
}
DTDDeclParameter parameter = new DTDDeclParameter(this, start, end);
parameters.add(parameter);
this.end = end; // updates end position of the node.
return parameter;
}
public void updateLastParameterEnd(int end) {
if (parameters != null && parameters.size() > 0) {
DTDDeclParameter last = parameters.get(parameters.size() - 1);
last.end = end;
this.end = end;
}
}
public List<DTDDeclParameter> getParameters() {
if (parameters == null) {
parameters = new ArrayList<>();
}
return parameters;
}
public void setDeclType(int start, int end) {
declType = new DTDDeclParameter(this, start, end);
}
public String getDeclType() {
if (declType != null) {
return declType.getParameter();
}
return null;
}
/**
* Returns the parameter name which references a DTD element declaration
* (<!ELEMENT) at the given offset and null otherwise.
*
* <p>
* <!ATTLIST element-name ... will return position of 'element-name' which
* references the <!ELEMENT element-name
* </p>
*
* @param offset the offset
* @return the parameter name which references a DTD element declaration
* (<!ELEMENT) at the given offset and null otherwise.
*/
public DTDDeclParameter getReferencedElementNameAt(int offset) {
if (isInNameParameter(offset)) {
return getNameParameter();
}
return null;
}
}
@@ -1,97 +0,0 @@
/*******************************************************************************
* Copyright (c) 2018 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.dom;
import org.eclipse.lemminx.utils.XMLPositionUtility;
import org.eclipse.lsp4j.Range;
/**
* DTDDeclParameter
*/
public class DTDDeclParameter implements DOMRange, TargetRange {
private final DTDDeclNode ownerNode;
String parameter;
int start, end;
public DTDDeclParameter(DTDDeclNode ownerNode, int start, int end) {
this.ownerNode = ownerNode;
this.start = start;
this.end = end;
}
@Override
public int getStart() {
return start;
}
@Override
public int getEnd() {
return end;
}
@Override
public DOMDocument getOwnerDocument() {
return getOwnerNode().getOwnerDocument();
}
public DOMDocumentType getOwnerDocType() {
return getOwnerNode().getOwnerDocType();
}
public DTDDeclNode getOwnerNode() {
return ownerNode;
}
public String getParameter() {
if (parameter == null) {
parameter = getOwnerDocType().getSubstring(start, end);
}
return parameter;
}
/**
* Will get the parameter with the first and last character removed
*
* Can be used to remove the quotations from a URL value...
*/
public String getParameterWithoutFirstAndLastChar() {
if (parameter == null) {
parameter = getOwnerDocType().getSubstring(start + 1, end - 1);
}
return parameter;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof DTDDeclParameter)) {
return false;
}
DTDDeclParameter temp = (DTDDeclParameter) obj;
return start == temp.start && end == temp.end;
}
@Override
public Range getTargetRange() {
return XMLPositionUtility.createRange(this);
}
@Override
public String getTargetURI() {
return getOwnerDocument().getDocumentURI();
}
}
@@ -1,196 +0,0 @@
/**
* Copyright (c) 2018 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
* http://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
*/
package org.eclipse.lemminx.dom;
import static org.eclipse.lemminx.utils.StringUtils.findEndWord;
import static org.eclipse.lemminx.utils.StringUtils.findStartWord;
import java.util.function.BiConsumer;
import java.util.function.Predicate;
/**
* DTD Element Declaration <!ELEMENT
*
* @see https://www.w3.org/TR/REC-xml/#dt-eldecl
*
*/
public class DTDElementDecl extends DTDDeclNode {
private static final Predicate<Character> isValidChar = (c) -> Character.isJavaIdentifierPart(c) || c == '-';
/**
* Formats:
*
* <!ELEMENT element-name category> or <!ELEMENT element-name (element-content)>
*
*/
public DTDDeclParameter category;
public DTDDeclParameter content;
public DTDElementDecl(int start, int end) {
super(start, end);
setDeclType(start + 2, start + 9);
}
@Override
public String getNodeName() {
return getName();
}
public String getCategory() {
return category != null ? category.getParameter() : null;
}
public void setCategory(int start, int end) {
category = addNewParameter(start, end);
}
public String getContent() {
return content != null ? content.getParameter() : null;
}
public void setContent(int start, int end) {
content = addNewParameter(start, end);
}
@Override
public short getNodeType() {
return DOMNode.DTD_ELEMENT_DECL_NODE;
}
/**
* Returns the offset of the end of tag <!ELEMENT
*
* @return the offset of the end of tag <!ELEMENT
*/
public int getEndElementTag() {
return getStart() + "<!ELEMENT".length();
}
/**
* Returns the parameter (start/end offset) at the given offset and null
* otherwise.
*
* <p>
* <!ELEMENT note (to,from,head|ing,body)> will return (start/end offset) of
* heading.
* </p>
*
* <p>
* <!ELEMENT n|ote (to,from,heading,body)> will return null.
* </p>
*
* @param offset the offset
* @return the parameter (start/end offset) at the given offset and null
* otherwise.
*/
public DTDDeclParameter getParameterAt(int offset) {
// Check if offset is in the <!ELEMENT nam|e
if (isInNameParameter(offset)) {
return null;
}
// We are after the <!ELEMENT name, search the parameter
String text = getOwnerDocument().getText();
// Find the start word offset from the left of the offset (ex : (head|ing) will
// return offset of 'h'
int paramStart = findStartWord(text, offset, isValidChar);
// Find the end word to the right of the offset (ex : (head|ing) will return
// offset of 'g'
int paramEnd = findEndWord(text, offset, isValidChar);
if (paramStart == -1 || paramEnd == -1) {
// no word
return null;
}
return new DTDDeclParameter(this, paramStart, paramEnd);
}
@Override
public DTDDeclParameter getReferencedElementNameAt(int offset) {
return getParameterAt(offset);
}
/**
* Collect parameters which matches the given target.
*
* @param target the target
* @param collector the collector to collect parameters.
*/
public void collectParameters(DTDDeclParameter target, BiConsumer<DTDDeclParameter, DTDDeclParameter> collector) {
DTDDeclParameter name = getNameParameter();
if (name == null) {
return;
}
int start = name.getEnd();
int end = getEnd();
String text = getOwnerDocument().getText();
int wordStart = -1;
int wordEnd = -1;
// Loop for content after <!ELEMENT element-name (
// to check if target checks words
// Ex : if 'svg' is the word to find (the target node)
// and we have <!ELEMENT element-name (svg, title, font-face)
// we must collect svg as child
for (int i = start; i < end; i++) {
char c = text.charAt(i);
// check if current character is valid for a word.
if (isValidChar.test(c)) {
if (wordStart == -1) {
// start of the word
wordStart = i;
}
} else if (wordStart != -1) {
// current character is not valid, it's the end of the word.
wordEnd = i;
}
if (wordStart != -1 && wordEnd != -1) {
// a word was found
boolean check = isMatchName(target.getParameter(), text, wordStart, wordEnd);
if (check) {
collector.accept(new DTDDeclParameter(this, wordStart, wordEnd), target);
}
wordStart = -1;
wordEnd = -1;
}
}
}
/**
* Returns true if the word in the given <code>text</code> which starts at
* <code>wordStart</code> offset and ends at <code>wordEnd</code> matches the
* given <code>searchWord</code>
*
* @param searchWord the word to search
* @param text the text
* @param wordStart the word start offset
* @param wordEnd the word end offset
* @return true if the word in the given <code>text</code> which starts at
* <code>wordStart</code> offset and ends at <code>wordEnd</code>
* matches the given <code>searchName</code>
*/
private static boolean isMatchName(String searchWord, String text, int wordStart, int wordEnd) {
int length = wordEnd - wordStart;
if (searchWord.length() != length) {
return false;
}
for (int j = 0; j < length; j++) {
if ((searchWord.charAt(j) != text.charAt(wordStart + j))) {
return false;
}
}
return true;
}
}
@@ -1,157 +0,0 @@
/**
* Copyright (c) 2018 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
* http://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
*/
package org.eclipse.lemminx.dom;
import org.w3c.dom.Entity;
import org.w3c.dom.Node;
/**
* DOM Entity declaration <!ENTITY
*
* @see https://www.w3.org/TR/REC-xml/#dt-entdecl
*/
public class DTDEntityDecl extends DTDDeclNode implements Entity {
/**
* Formats:
*
* <!ENTITY entity-name "entity-value">
*
* or
*
* <!ENTITY % entity-name "entity-value">
*
* or
*
* <!ENTITY % entity-name SYSTEM "systemId">
*
* or
*
* <!ENTITY % entity-name PUBLIC "publicId" "systemId">
*
* or
*
* <!ENTITY % entity-name SYSTEM "systemId" NDATA name>
*
* or
*
* <!ENTITY % entity-name PUBLIC "publicId" "systemId" NDATA name>
*/
DTDDeclParameter percent;
DTDDeclParameter value;
DTDDeclParameter kind;
DTDDeclParameter publicId;
DTDDeclParameter systemId;
public DTDEntityDecl(int start, int end) {
super(start, end);
setDeclType(start + 2, start + 8);
}
public String getPercent() {
return percent != null ? percent.getParameter() : null;
}
public void setPercent(int start, int end) {
percent = addNewParameter(start, end);
}
public String getValue() {
return value != null ? value.getParameterWithoutFirstAndLastChar() : null;
}
public void setValue(int start, int end) {
value = addNewParameter(start, end);
}
public String getKind() {
return kind != null ? kind.getParameter() : null;
}
public void setKind(int start, int end) {
kind = addNewParameter(start, end);
}
@Override
public short getNodeType() {
return Node.ENTITY_NODE;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Entity#getInputEncoding()
*/
@Override
public String getInputEncoding() {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Entity#getNotationName()
*/
@Override
public String getNotationName() {
return getValue();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Entity#getPublicId()
*/
@Override
public String getPublicId() {
return publicId != null ? publicId.getParameterWithoutFirstAndLastChar() : null;
}
public void setPublicId(int start, int end) {
publicId = addNewParameter(start, end);
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Entity#getSystemId()
*/
@Override
public String getSystemId() {
return systemId != null ? systemId.getParameterWithoutFirstAndLastChar() : null;
}
public void setSystemId(int start, int end) {
systemId = addNewParameter(start, end);
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Entity#getXmlEncoding()
*/
@Override
public String getXmlEncoding() {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.Entity#getXmlVersion()
*/
@Override
public String getXmlVersion() {
throw new UnsupportedOperationException();
}
}
@@ -1,72 +0,0 @@
/*******************************************************************************
* Copyright (c) 2018 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.dom;
/**
* DTDNotationDecl
*/
public class DTDNotationDecl extends DTDDeclNode {
/**
* Format:
*
* <!NOTATION Name PUBLIC PublicID>
*
* or
*
* <!NOTATION Name PUBLIC PublicID SystemID>
*
* or
*
* <!NOTATION Name SYSTEM SystemID>
*/
DTDDeclParameter kind;
DTDDeclParameter publicId;
DTDDeclParameter systemId;
public DTDNotationDecl(int start, int end) {
super(start, end);
setDeclType(start + 2, start + 10);
}
void setKind(int start, int end) {
kind = addNewParameter(start, end);
}
public String getKind() {
return kind != null ? kind.getParameter() : null;
}
void setPublicId(int start, int end) {
publicId = addNewParameter(start, end);
}
public String getPublicId() {
return publicId != null ? publicId.getParameterWithoutFirstAndLastChar() : null;
}
void setSystemId(int start, int end) {
systemId = addNewParameter(start, end);
}
public String getSystemId() {
return systemId != null ? systemId.getParameterWithoutFirstAndLastChar() : null;
}
@Override
public short getNodeType() {
return DOMNode.DTD_NOTATION_DECL;
}
}
@@ -1,34 +0,0 @@
/*******************************************************************************
* Copyright (c) 2018 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:
* Angelo Zerr. - initial API and implementation
*
*******************************************************************************/
package org.eclipse.lemminx.dom;
public class LineIndentInfo {
private final String lineDelimiter;
private final String whitespacesIndent;
public LineIndentInfo(String lineDelimiter, String whitespacesIndent) {
this.lineDelimiter = lineDelimiter;
this.whitespacesIndent = whitespacesIndent;
}
public String getLineDelimiter() {
return lineDelimiter;
}
public String getWhitespacesIndent() {
return whitespacesIndent;
}
}
@@ -1,42 +0,0 @@
/**
* Copyright (c) 2018 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
* http://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
*/
package org.eclipse.lemminx.dom;
/**
*
* The declared "xsi:noNamespaceSchemaLocation"
*/
public class NoNamespaceSchemaLocation {
private final DOMAttr attr;
public NoNamespaceSchemaLocation(DOMAttr attr) {
this.attr = attr;
}
public DOMAttr getAttr() {
return attr;
}
/**
* Returns the location declared in the attribute value of
* "xsi:noNamespaceSchemaLocation"
*
* @return the location declared in the attribute value of
* "xsi:noNamespaceSchemaLocation"
*/
public String getLocation() {
return attr.getValue();
}
}
@@ -1,82 +0,0 @@
/**
* Copyright (c) 2018 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
* http://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
*/
package org.eclipse.lemminx.dom;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
*
* The declared "xsi:schemaLocation"
*/
public class SchemaLocation {
private final Map<String, SchemaLocationHint> schemaLocationValuePairs;
private final DOMAttr attr;
// The text to match is of the form:
// http://example.org/schema/root root.xsd http://example.org/schema/bison bison.xsd http://example.org/schema/potato potato.xsd
private static final Pattern SCHEMA_LOCATION_PAIR_PATTERN = Pattern.compile("\\s*([^\\s]+)\\s+([^\\s]+)\\s*");
public SchemaLocation(DOMAttr attr) {
this.attr = attr;
this.schemaLocationValuePairs = new HashMap<>();
String value = attr.getValue();
Matcher locPairMatcher = SCHEMA_LOCATION_PAIR_PATTERN.matcher(value);
while (locPairMatcher.find()) {
String namespaceURI = locPairMatcher.group(1);
String locationHint = locPairMatcher.group(2);
if (namespaceURI == null || locationHint == null) {
break;
}
DOMNode valNode = attr.getNodeAttrValue();
// http://example.org/schema/root |root.xsd http://example.org/schema/bison bison.xsd
int start = valNode.getStart() + locPairMatcher.start(2) + 1;
// http://example.org/schema/root root.xsd| http://example.org/schema/bison bison.xsd
int end = valNode.getStart() + locPairMatcher.end(2) + 1;
schemaLocationValuePairs.put(namespaceURI,
new SchemaLocationHint(start, end, locationHint, this));
}
}
/**
* Returns the location hint, as a string, that is associated with the given namespace URI.
*
* If the given namespace URI was not referred to in this xsi:schemaLocation, then null is returned.
*
* @param namespaceURI The namespace URI to find the location hint for
* @return The associated location hint, as a string, or null
* if the namespace was not referred to in xsi:schemaLocation
*/
public SchemaLocationHint getLocationHint(String namespaceURI) {
return schemaLocationValuePairs.get(namespaceURI);
}
public DOMAttr getAttr() {
return attr;
}
/**
* Returns all the location hints given in this xsi:schemaLocation attribute
*
* @return A Collection of all the location hints as <code>SchemaLocationHint</code>
*/
public Collection<SchemaLocationHint> getSchemaLocationHints() {
return schemaLocationValuePairs.values();
}
}
@@ -1,84 +0,0 @@
/**
* Copyright (c) 2020 Red Hat Inc.
* 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.dom;
/**
* Represents one of the location hints provided in an xsi:schemaLocation
* attribute.
*/
public class SchemaLocationHint implements DOMRange {
private final int start, end;
private final String hint;
private final SchemaLocation parent;
/**
* Create a new SchemaLocationHint object that represents one of the location
* hints given in the parent <code>SchemaLocation</code>.
*
* @param start The offset from the beginning of the document where this
* location hint starts
* @param end The offset from the beginning of the document where this
* location hint ends
* @param hint The hint to the location of a schema (A URI that points to a
* schema)
* @param parent The <code>SchemaLocation</code> in which this hint was given
*/
public SchemaLocationHint(int start, int end, String hint, SchemaLocation parent) {
this.start = start;
this.end = end;
this.hint = hint;
this.parent = parent;
}
/**
* Returns the location hint that this SchemaLocationHint represents
*
* @return The location hint, a URI to a schema, as a String
*/
public String getHint() {
return this.hint;
}
/**
* Returns the offset from the beginning of the document where this location
* hint starts
*
* @return The offset from the beginning of the document where this location
* hint starts
*/
@Override
public int getStart() {
return this.start;
}
/**
* Returns the offset from the beginning of the document where this location
* hint ends
*
* @return The offset from the beginning of the document where this location
* hint ends
*/
@Override
public int getEnd() {
return this.end;
}
@Override
public DOMDocument getOwnerDocument() {
return parent.getAttr().getOwnerDocument();
}
}
@@ -1,36 +0,0 @@
/**
* Copyright (c) 2020 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.dom;
import org.eclipse.lsp4j.Range;
/**
* Target range API.
*
*/
public interface TargetRange {
/**
* Returns the target range.
*
* @return the target range.
*/
Range getTargetRange();
/**
* Returns the target URI.
*
* @return the target URI.
*/
String getTargetURI();
}
@@ -1,142 +0,0 @@
/*******************************************************************************
* Copyright (c) 2020 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
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx.dom;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import org.eclipse.lemminx.extensions.xerces.xmlmodel.XMLModelDeclaration;
import org.w3c.dom.ProcessingInstruction;
/**
* XML model processing instruction.
*
* <pre>
* &lt;?xml-model href="http://www.docbook.org/xml/5.0/xsd/docbook.xsd"?&gt;
* </pre>
*
*
* @see https://www.w3.org/TR/xml-model/
*/
public class XMLModel {
private static final String XML_MODEL_PI = "xml-model";
private final DOMProcessingInstruction processingInstruction;
private XMLModelDeclaration declaration;
private DOMRange hrefNode;
private static class SimpleDOMRange implements DOMRange {
private final int start;
private final int end;
private final DOMDocument ownerDocument;
public SimpleDOMRange(int start, int end, DOMDocument ownerDocument) {
this.start = start;
this.end = end;
this.ownerDocument = ownerDocument;
}
@Override
public int getStart() {
return start;
}
@Override
public int getEnd() {
return end;
}
@Override
public DOMDocument getOwnerDocument() {
return ownerDocument;
}
}
public XMLModel(DOMProcessingInstruction processingInstruction) {
this.processingInstruction = processingInstruction;
}
/**
* Returns the location of the referenced schema
*
* @return the location of the referenced schema
*/
public String getHref() {
XMLModelDeclaration declaration = getModelDeclaration();
return declaration != null ? declaration.getHref() : null;
}
private XMLModelDeclaration getModelDeclaration() {
String data = processingInstruction.getData();
if (data == null) {
return null;
}
if (declaration == null) {
declaration = XMLModelDeclaration.parse(data.toCharArray(), 0, data.length());
}
return declaration;
}
/**
* Returns the declared xml-model list.
*
* @param document the DOM document.
*
* @return the declared xml-model list.
*/
static List<XMLModel> createXMLModels(DOMDocument document) {
List<DOMNode> children = document.getChildren();
if (children != null && !children.isEmpty()) {
return children.stream().filter(XMLModel::isXMLModel)
.map(node -> new XMLModel((DOMProcessingInstruction) node)).collect(Collectors.toList());
}
return Collections.emptyList();
}
/**
* Returns true if the given node is a xml-model processing instruction and
* false otherwise.
*
* @param node the DOM node.
* @return true if the given node is a xml-model processing instruction and
* false otherwise.
*/
public static boolean isXMLModel(DOMNode node) {
return node.isProcessingInstruction() && XML_MODEL_PI.equals(((ProcessingInstruction) node).getTarget());
}
/**
* Returns the href range and null otherwise.
*
* @return the href range and null otherwise.
*/
public DOMRange getHrefNode() {
if (hrefNode == null) {
XMLModelDeclaration declaration = getModelDeclaration();
if (declaration == null || declaration.getHref() == null) {
return null;
}
int start = processingInstruction.getStartContent() + declaration.getHrefOffset() - 1;
int end = start + declaration.getHref().length() + 2;
hrefNode = new SimpleDOMRange(start, end, processingInstruction.getOwnerDocument());
}
return hrefNode;
}
}
@@ -1,107 +0,0 @@
/**
* Copyright (c) 2018 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
* http://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Initial code from https://github.com/Microsoft/vscode-html-languageservice
* Initial copyright Copyright (C) Microsoft Corporation. All rights reserved.
* Initial license: MIT
*
* Contributors:
* - Microsoft Corporation: Initial code, written in TypeScript, licensed under MIT license
* - Angelo Zerr <angelo.zerr@gmail.com> - translation and adaptation to Java
*/
package org.eclipse.lemminx.dom.parser;
import java.util.regex.Pattern;
public class Constants {
public final static int _EXL = "!".codePointAt(0);
public final static int _MIN = "-".codePointAt(0);
public final static int _UDS = "_".codePointAt(0);
public final static int _DDT = ":".codePointAt(0);
public final static int _DOT = ".".codePointAt(0);
public final static int _LAN = "<".codePointAt(0);
public final static int _RAN = ">".codePointAt(0);
public final static int _FSL = "/".codePointAt(0);
public final static int _EQS = "=".codePointAt(0);
public final static int _CMA = ",".codePointAt(0);
public final static int _DQO = "\"".codePointAt(0);
public final static int _SQO = "\"".codePointAt(0);
public final static int _SIQ = "\'".codePointAt(0);
public final static int _NWL = "\n".codePointAt(0);
public final static int _CAR = "\r".codePointAt(0);
public final static int _LFD = "\f".codePointAt(0);
public final static int _WSP = " ".codePointAt(0);
public final static int _TAB = "\t".codePointAt(0);
public final static int _OSB = "[".codePointAt(0);
public final static int _CSB = "]".codePointAt(0);
public final static int _ORB = "(".codePointAt(0);
public final static int _CRB = ")".codePointAt(0);
public final static int _OCB = "{".codePointAt(0);
public final static int _CCB = "}".codePointAt(0);
public final static int _CVL = "C".codePointAt(0);
public final static int _DVL = "D".codePointAt(0);
public final static int _AVL = "A".codePointAt(0);
public final static int _TVL = "T".codePointAt(0);
public final static int _OVL = "O".codePointAt(0);
public final static int _YVL = "Y".codePointAt(0);
public final static int _PVL = "P".codePointAt(0);
public final static int _EVL = "E".codePointAt(0);
public final static int _LVL = "L".codePointAt(0);
public final static int _MVL = "M".codePointAt(0);
public final static int _NVL = "N".codePointAt(0);
public final static int _IVL = "I".codePointAt(0);
public final static int _SVL = "S".codePointAt(0);
public final static int _QMA = "?".codePointAt(0);
public final static int _XVL = "x".codePointAt(0);
public final static int _mVL = "m".codePointAt(0);
public final static int _lVL = "l".codePointAt(0);
public final static int _PCT = "%".codePointAt(0);
public final static int _AST = "*".codePointAt(0);
public final static int _PLS = "+".codePointAt(0);
public static final Pattern ENTITY_NAME_REGEX = Pattern.compile("");
public static final Pattern ELEMENT_NAME_REGEX = Pattern.compile("^[_:\\w][_:\\w-.\\d]*");
public static final Pattern ATTRIBUTE_NAME_REGEX = Pattern.compile("^[^\\s\\?\"'<>/=\\x00-\\x0F\\x7F\\x80-\\x9F]*");
public static final Pattern ATTRIBUTE_VALUE_REGEX = Pattern.compile("^(\"[^\"]*\"?)|(\'[^\']*\'?)");
public static final Pattern URL_VALUE_REGEX = Pattern.compile("^(\"[^<>\"]*\")|(\'[^<>\']*\')");
public static final Pattern PROLOG_NAME_OPTIONS = Pattern.compile("^(xml)[\\s<>?]?");
public static final Pattern PI_TAG_NAME = Pattern.compile("^[a-zA-Z0-9]+");
// Add coming processing instructions that are defined to have attributes as
// content
public static final Pattern PI_WITH_VARIABLES = Pattern.compile("^(xml-stylesheet)[\\s<>?]?");
public static final Pattern DOCTYPE_KIND_OPTIONS = Pattern.compile("^(PUBLIC|SYSTEM)([\\s<>\"'])");
public static final Pattern DTD_ELEMENT_CATEGORY = Pattern.compile("^(EMPTY|ANY)([\\s<>\"'])");
public static final Pattern DTD_ELEMENT_CONTENT = Pattern.compile("^(\\((([^\\s,]+,)*[^\\s,]+)\\))|\\(\\)");
public static final Pattern DTD_PCDATA = Pattern.compile("^#PCDATA");
public static final Pattern DTD_ATTLIST_ATTRIBUTE_TYPE = Pattern
.compile("^(CDATA|IDREFS|IDREF|ID|NMTOKENS|NMTOKEN|ENTITIES|ENTITY|NOTATION|xml:|\\(.*\\))([\\s<>\"'])");
public static final Pattern DTD_ATTLIST_ATTRIBUTE_VALUE = Pattern
.compile("^(#REQUIRED|#IMPLIED|\".*\"|#FIXED \".*\")([\\s<>\"'])");
public static final Pattern DTD_ENTITY_VALUE = Pattern.compile("^\".*\"");
public static final Pattern DOCTYPE_NAME = Pattern.compile("^[_:\\w][_:\\w-.\\d]*");
public static final Pattern DOCUMENTATION_CONTENT = Pattern.compile(".*<[\\S]+:?documentation[^\\>]*>(.*)<\\/[\\S]+:?documentation[\\s]*>.*");
}
@@ -1,336 +0,0 @@
/**
* Copyright (c) 2018 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
* http://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Initial code from https://github.com/Microsoft/vscode-html-languageservice
* Initial copyright Copyright (C) Microsoft Corporation. All rights reserved.
* Initial license: MIT
*
* Contributors:
* - Microsoft Corporation: Initial code, written in TypeScript, licensed under MIT license
* - Angelo Zerr <angelo.zerr@gmail.com> - translation and adaptation to Java
*/
package org.eclipse.lemminx.dom.parser;
import static org.eclipse.lemminx.dom.parser.Constants._CAR;
import static org.eclipse.lemminx.dom.parser.Constants._CSB;
import static org.eclipse.lemminx.dom.parser.Constants._LAN;
import static org.eclipse.lemminx.dom.parser.Constants._LFD;
import static org.eclipse.lemminx.dom.parser.Constants._NWL;
import static org.eclipse.lemminx.dom.parser.Constants._OSB;
import static org.eclipse.lemminx.dom.parser.Constants._RAN;
import static org.eclipse.lemminx.dom.parser.Constants._TAB;
import static org.eclipse.lemminx.dom.parser.Constants._WSP;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Multi line stream.
*
*/
public class MultiLineStream {
private static final Predicate<Integer> WHITESPACE_PREDICATE = ch -> {
return ch == _WSP || ch == _TAB || ch == _NWL || ch == _LFD || ch == _CAR;
};
private final String source;
private final int len;
private int position;
private final Map<Pattern, Matcher> regexpCache;
public MultiLineStream(String source, int position) {
this.source = source;
this.len = source.length();
this.position = position;
this.regexpCache = new HashMap<>();
}
public boolean eos() {
return this.len <= this.position;
}
public String getSource() {
return this.source;
}
public int pos() {
return this.position;
}
public void goBackTo(int pos) {
this.position = pos;
}
public void goBack(int n) {
this.position -= n;
}
public void advance(int n) {
this.position += n;
}
public void goToEnd() {
this.position = len;
}
/*
* public int nextChar() { // return this.source.codePointAt(this.position++) ||
* 0; int next = this.source.codePointAt(this.position++); return next >= 0 ?
* next : 0; }
*/
public int peekChar() {
return peekChar(0);
}
/**
* Peeks at next char at position + n. peekChar() == peekChar(0)
* @param n
* @return
*/
public int peekChar(int n) {
int pos = this.position + n;
if (pos >= len) {
return -1;
}
return this.source.codePointAt(pos);
}
/**
* Peeks at the char at position 'offset' of the whole document
* @param offset
* @return
*/
public int peekCharAtOffset(int offset) {
if (offset >= len || offset < 0) {
return -1;
}
return this.source.codePointAt(offset);
}
public boolean advanceIfChar(int ch) {
if (ch == peekChar()) {
this.position++;
return true;
}
return false;
}
public boolean advanceIfChars(int... ch) {
int i;
if (this.position + ch.length > this.len) {
return false;
}
for (i = 0; i < ch.length; i++) {
if (peekChar(i) != ch[i]) {
return false;
}
}
this.advance(i);
return true;
}
public int advanceIfAnyOfChars(int... ch) {
int i;
if (this.position + 1 > this.len) {
return -1;
}
for (i = 0; i < ch.length; i++) {
if (advanceIfChar(ch[i])) {
return ch[i];
}
}
return -1;
}
public String advanceIfRegExp(Pattern regex) {
Matcher match = getCachedMatcher(regex);
// Initialize start region where search must be started.
match.region(this.position, this.len);
if (match.find()) {
this.position = match.end();
return match.group(0);
}
return "";
}
/**
* Advances stream on regex, but will grab the first group
* @param regex
* @return
*/
public String advanceIfRegExpGroup1(Pattern regex) {
Matcher match = getCachedMatcher(regex);
// Initialize start region where search must be started.
match.region(this.position, this.len);
if (match.find()) {
this.position = match.end(1);
return match.group(1);
}
return "";
}
/**
* Advances stream.position no matter what until it hits ch or eof(this.len)
*
* @return boolean: was the char found
*/
public boolean advanceUntilChar(int ch) {
while (this.position < this.len) {
if (peekChar() == ch) {
return true;
}
this.advance(1);
}
return false;
}
/**
* Will advance until any of the provided chars are encountered
*/
public boolean advanceUntilAnyOfChars(int... ch) {
while (this.position < this.len) {
for (int i = 0; i < ch.length; i++) {
if (peekChar() == ch[i]) {
return true;
}
}
this.advance(1);
}
return false;
}
/**
* Will advance the stream position until ch or '<'
*/
public boolean advanceUntilCharOrNewTag(int ch) {
while (this.position < this.len) {
if (peekChar() == ch || peekChar() == _LAN) {
return true;
}
this.advance(1);
}
return false;
}
/**
* Will advance the stream position until 'closingBracket' or using a stack
* to consider possible open/closed bracket pairs in between.
*
* 'closingBracket' should be the closing bracket eg: > | ]
*/
public boolean advanceUntilCharUsingStack(int closingBracket) {
int openingBracket;
if(closingBracket == _RAN) { // >
openingBracket = _LAN;// <
}
else if(closingBracket == _CSB) { // ]
openingBracket = _OSB; // [
}
else {
return false; // The provided closingBracket is not implemented
}
int stack = 0;
while (this.position < this.len) {
if(peekChar() == openingBracket) {
stack ++;
}
else if(peekChar() == closingBracket) {
if(stack == 0) {
return true;
}
stack--;
}
this.advance(1);
}
return false;
}
public boolean advanceUntilChars(int... ch) {
while (this.position + ch.length <= this.len) {
int i = 0;
for (; i < ch.length && peekChar(i) == ch[i]; i++) {
}
if (i == ch.length) {
return true;
}
this.advance(1);
}
this.goToEnd();
return false;
}
/**
* Advances until it matches int[] ch OR it hits '<' If this returns true, peek
* if next char is '<' to check which case was hit
*/
public boolean advanceUntilCharsOrNewTag(int... ch) {
while (this.position + ch.length <= this.len) {
int i = 0;
if (peekChar(0) == _LAN) { // <
return true;
}
for (; i < ch.length && peekChar(i) == ch[i]; i++) {
}
if (i == ch.length) {
return true;
}
this.advance(1);
}
this.goToEnd();
return false;
}
/**
* Advances until it reaches a whitespace character
*/
public boolean skipWhitespace() {
int n = this.advanceWhileChar(WHITESPACE_PREDICATE);
return n > 0;
}
public int advanceWhileChar(Predicate<Integer> condition) {
int posNow = this.position;
while (this.position < this.len && condition.test(peekChar())) {
this.position++;
}
return this.position - posNow;
}
/**
* Returns the cached matcher from the given regex.
*
* @param regex the regex pattern.
* @return the cached matcher from the given regex.
*/
private Matcher getCachedMatcher(Pattern regex) {
Matcher matcher = regexpCache.get(regex);
if (matcher == null) {
matcher = regex.matcher(source);
regexpCache.put(regex, matcher);
} else {
matcher.reset(); // Cached regex caused issues, needed to reset it.
}
return matcher;
}
public int getLastNonWhitespaceOffset() {
int posNow = this.position;
while (posNow > 0 && WHITESPACE_PREDICATE.test(peekCharAtOffset(posNow - 1))) {
posNow--;
}
return posNow;
}
}
@@ -1,56 +0,0 @@
/**
* Copyright (c) 2018 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
* http://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Initial code from https://github.com/Microsoft/vscode-html-languageservice
* Initial copyright Copyright (C) Microsoft Corporation. All rights reserved.
* Initial license: MIT
*
* Contributors:
* - Microsoft Corporation: Initial code, written in TypeScript, licensed under MIT license
* - Angelo Zerr <angelo.zerr@gmail.com> - translation and adaptation to Java
*/
package org.eclipse.lemminx.dom.parser;
/**
* Scanner API.
*
*/
public interface Scanner {
TokenType scan();
TokenType getTokenType();
/**
* Starting offset position of the current token
*
* @return int of token's start offset
*/
int getTokenOffset();
int getTokenLength();
/**
* Ending offset position of the current token
*
* @return int of token's end offset
*/
int getTokenEnd();
String getTokenText();
String getTokenError();
ScannerState getScannerState();
/**
* @return True if the token's Text is empty or all whitespace
*/
boolean isTokenTextBlank();
}
@@ -1,38 +0,0 @@
/**
* Copyright (c) 2018 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
* http://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Initial code from https://github.com/Microsoft/vscode-html-languageservice
* Initial copyright Copyright (C) Microsoft Corporation. All rights reserved.
* Initial license: MIT
*
* Contributors:
* - Microsoft Corporation: Initial code, written in TypeScript, licensed under MIT license
* - Angelo Zerr <angelo.zerr@gmail.com> - translation and adaptation to Java
* - Nikolas Komonen <nkomonen@redhat.com> - additions for CDATA and Processing Instruction
*/
package org.eclipse.lemminx.dom.parser;
/**
* XML Scanner state.
*
*/
public enum ScannerState {
WithinContent, AfterOpeningStartTag, AfterOpeningEndTag, WithinProlog, WithinTag, WithinEndTag,
WithinComment, AfterAttributeName, BeforeAttributeValue, WithinCDATA, AfterClosingCDATATag, StartCDATATag,
AfterPrologOpen, PrologOrPI, WithinPI,
// DTD
DTDWithinDoctype, DTDAfterDoctypeName, DTDAfterDoctypePUBLIC, DTDAfterDoctypeSYSTEM,
DTDAfterDoctypePublicId,DTDWithinContent, DTDWithinElement, DTDWithinAttlist, DTDWithinEntity,
DTDElementAfterName, DTDElementWithinContent, DTDAfterAttlistName, DTDAfterAttlistElementName,
DTDAfterAttlistAttributeName, DTDAfterAttlistAttributeType, DTDAfterEntityName, DTDUnrecognizedParameters,
DTDWithinNotation, DTDAfterNotationName, DTDAfterNotationPUBLIC, DTDAfterNotationSYSTEM,
DTDAfterNotationPublicId, DTDAfterEntityPUBLIC, DTDAfterEntitySYSTEM, DoctypeUnrecognizedParameters;
}
@@ -1,102 +0,0 @@
/**
* Copyright (c) 2018 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
* http://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Initial code from https://github.com/Microsoft/vscode-html-languageservice
* Initial copyright Copyright (C) Microsoft Corporation. All rights reserved.
* Initial license: MIT
*
* Contributors:
* - Microsoft Corporation: Initial code, written in TypeScript, licensed under MIT license
* - Angelo Zerr <angelo.zerr@gmail.com> - translation and adaptation to Java
*/
package org.eclipse.lemminx.dom.parser;
/**
* XML Token type.
*
*/
public enum TokenType {
StartCommentTag,
Comment,
EndCommentTag,
CDATATagOpen,
CDATAContent,
CDATATagClose,
StartTagOpen,
StartTagClose,
StartTagSelfClose,
StartTag,
EndTagOpen,
EndTagClose,
EndTag,
DelimiterAssign,
AttributeName,
AttributeValue,
StartPrologOrPI,
PrologName,
PIName,
PIContent,
PIEnd,
PrologEnd,
Content,
Whitespace,
Unknown,
EOS,
// DTD
DTDStartDoctypeTag,
DTDDoctypeName,
DTDDocTypeKindPUBLIC,
DTDDocTypeKindSYSTEM,
DTDDoctypePublicId,
DTDDoctypeSystemId,
DTDEndDoctypeTag,
DTDStartInternalSubset,
DTDEndInternalSubset,
// DTD Element declaration
DTDStartElement,
DTDElementDeclName,
DTDElementCategory,
DTDStartElementContent,
DTDElementContent,
DTDEndElementContent,
// DTD AttrList declaration
DTDStartAttlist,
DTDAttlistElementName,
DTDAttlistAttributeValue,
DTDAttlistAttributeType,
DTDAttlistAttributeName,
// DTD Entity
DTDStartEntity,
DTDEntityPercent,
DTDEntityKindPUBLIC,
DTDEntityKindSYSTEM,
DTDEntityPublicId,
DTDEntitySystemId,
DTDEntityName,
DTDEntityValue,
// DTD Notation
DTDStartNotation,
DTDNotationName,
DTDNotationKindPUBLIC,
DTDNotationKindSYSTEM,
DTDNotationPublicId,
DTDNotationSystemId,
//For any DTD Decl Tag that has an unrecognized parameter
DTDUnrecognizedParameters,
//End of any DTD Decl Tag
DTDEndTag;
}
File diff suppressed because it is too large Load Diff
@@ -1,51 +0,0 @@
/*******************************************************************************
* Copyright (c) 2020 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
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx.extensions.catalog;
import java.nio.file.Paths;
import org.eclipse.lemminx.dom.DOMAttr;
import org.eclipse.lemminx.dom.DOMElement;
import org.eclipse.lemminx.dom.DOMRange;
import org.eclipse.lemminx.utils.StringUtils;
import org.eclipse.lsp4j.jsonrpc.validation.NonNull;
/**
* Represents a catalog entry that uses the "catalog" attribute to reference an external document
*/
public class CatalogCatalogEntry extends CatalogEntry {
public CatalogCatalogEntry(@NonNull String baseURI, DOMElement entryElement) {
super(baseURI, entryElement);
}
@Override
public DOMRange getLinkRange() {
DOMAttr catalogAttr = CatalogUtils.getCatalogEntryCatalog(getEntryElement());
if (catalogAttr == null) {
return null;
}
return catalogAttr.getNodeAttrValue();
}
@Override
public String getResolvedURI() {
DOMAttr catalogAttr = CatalogUtils.getCatalogEntryCatalog(getEntryElement());
if (catalogAttr == null) {
return null;
}
String lastSegment = catalogAttr.getValue();
if (StringUtils.isBlank(lastSegment)) {
return null;
}
return Paths.get(getBaseURI(), lastSegment).toString();
}
}
@@ -1,67 +0,0 @@
/*******************************************************************************
* Copyright (c) 2020 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
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx.extensions.catalog;
import org.eclipse.lemminx.dom.DOMElement;
import org.eclipse.lemminx.dom.DOMRange;
/**
* Represents a catalog entry that references an external document
*/
public abstract class CatalogEntry {
private final String baseURI;
private final DOMElement entryElement;
/**
*
* @param baseURI <catalog>'s xml:base + <group>'s xml:base (if in a
* <group>)
* @param entryElement the element that corresponds with this catalog entry
*/
protected CatalogEntry(String baseURI, DOMElement entryElement) {
this.baseURI = baseURI;
this.entryElement = entryElement;
}
/**
* Returns the base URI for this catalog entry
*
* @return the base URI for this catalog entry
*/
public String getBaseURI() {
return baseURI;
}
/**
* Returns the element that corresponds with this catalog entry
*
* @return the element that corresponds with this catalog entry
*/
protected DOMElement getEntryElement() {
return entryElement;
}
/**
* Returns the range in the document where the link to an external document is,
* or null if this catalog entry does not refer to an external document
*
* @return the range in the document where the link to an external document is
*/
public abstract DOMRange getLinkRange();
/**
* Returns the URI for the document that this catalog entry references, or null
* if this catalog entry does not refer to an external document
*
* @return the URI for the document that this catalog entry references
*/
public abstract String getResolvedURI();
}
@@ -1,198 +0,0 @@
/*******************************************************************************
* Copyright (c) 2020 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
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx.extensions.catalog;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.eclipse.lemminx.dom.DOMAttr;
import org.eclipse.lemminx.dom.DOMDocument;
import org.eclipse.lemminx.dom.DOMElement;
import org.eclipse.lemminx.dom.DOMNode;
import org.eclipse.lemminx.utils.DOMUtils;
import org.eclipse.lsp4j.jsonrpc.validation.NonNull;
/**
* Utility functions for working with XML catalog documents
*/
public class CatalogUtils {
/**
* The catalog entries that have a 'uri' attribute
*/
private static final Collection<String> HAS_URI_ATTRIBUTE = Arrays.asList("public", "system", "uri", "systemSuffix",
"uriSuffix");
/**
* The catalog entries that have a 'catalog' attribute
*/
private static final Collection<String> HAS_CATALOG_ATTRIBUTE = Arrays.asList("delegatePublic", "delegateSystem",
"delegateUri", "nextCatalog");
private static final String XML_BASE_ATTRIBUTE = "xml:base";
private static final String CATALOG_ENTITY_NAME = "catalog";
private static final String GROUP_ENTITY_NAME = "group";
private static final String URI_ATTRIBUTE_NAME = "uri";
private static final String CATALOG_ATTRIBUTE_NAME = "catalog";
/**
* Returns a list of all the catalog entries in the given document, or an empty
* list if the document is not a catalog.
*
* @return a list of all the catalog entries in the given document, or an empty
* list if the document is not a catalog.
*/
public static List<CatalogEntry> getCatalogEntries(DOMDocument document) {
if (!DOMUtils.isCatalog(document)) {
return Collections.emptyList();
}
for (DOMNode n : document.getChildren()) {
if (n.isElement() && CATALOG_ENTITY_NAME.equals(n.getNodeName())) {
return collectCatalogEntries((DOMElement) n);
}
}
return Collections.emptyList();
}
/**
* Returns the uri attribute node of the given catalog entry or null if there is
* no uri attribute
*
* @param element The catalog entry to get the uri attribute of
* @return the uri attribute node of the given catalog entry or null if there is
* no uri attribute
*/
public static DOMAttr getCatalogEntryURI(DOMElement element) {
return element.getAttributeNode(URI_ATTRIBUTE_NAME);
}
/**
* Returns the catalog attribute node of the given catalog entry or null if
* there is no catalog attribute
*
* @param element The catalog entry to get the catalog attribute of
* @return the catalog attribute node of the given catalog entry or null if
* there is no catalog attribute
*/
public static DOMAttr getCatalogEntryCatalog(DOMElement element) {
return element.getAttributeNode(CATALOG_ATTRIBUTE_NAME);
}
/**
* Returns true if this element is a catalog entry that is required to have the
* 'uri' attribute and false otherwise
*
* @param element The element to check
* @return true if this element is an catalog entry that is required to have the
* 'uri' attribute and false otherwise
*/
private static boolean isCatalogEntryWithURI(DOMElement element) {
return HAS_URI_ATTRIBUTE.contains(element.getNodeName());
}
/**
* Returns true if this element is a catalog entry that is required to have the
* 'catalog' attribute and false otherwise
*
* @param element The element to check
* @return true if this element is an catalog entry that is required to have the
* 'catalog' attribute and false otherwise
*/
private static boolean isCatalogEntryWithCatalog(DOMElement element) {
return HAS_CATALOG_ATTRIBUTE.contains(element.getNodeName());
}
/**
* Returns true if the given element is a group element and false otherwise
*
* @param element The element to check
* @return true if the given element is a group element and false otherwise
*/
private static boolean isGroupCatalogEntry(DOMElement element) {
return GROUP_ENTITY_NAME.equals(element.getNodeName());
}
/**
* Get a list of all catalog entry elements that are in the given catalog
*
* @param catalog the catalog element to collect the entries for
* @return A list of all the catalog entities in this catalog
*/
private static List<CatalogEntry> collectCatalogEntries(@NonNull DOMElement catalog) {
List<CatalogEntry> entries = new ArrayList<>();
String baseURI = catalog.getAttribute(XML_BASE_ATTRIBUTE);
baseURI = baseURI == null ? "" : baseURI;
for (DOMNode node : catalog.getChildren()) {
if (node.isElement()) {
DOMElement element = (DOMElement) node;
CatalogEntry catalogEntry = createCatalogEntry(baseURI, element);
if (catalogEntry != null) {
entries.add(catalogEntry);
} else if (isGroupCatalogEntry(element)) {
entries.addAll(collectGroupEntries(element, baseURI));
}
}
}
return entries;
}
/**
* Get a list of all catalog entry elements that are in the given group
*
* @param group the group element to collect the entries for
* @param baseURI the baseURI of the catalog
* @return A list of all the catalog entities in this group
*/
private static List<CatalogEntry> collectGroupEntries(DOMElement group, @NonNull String baseURI) {
List<CatalogEntry> entries = new ArrayList<>();
String groupSegment = group.getAttribute(XML_BASE_ATTRIBUTE);
if (groupSegment != null) {
baseURI = Paths.get(baseURI, groupSegment).toString();
}
for (DOMNode node : group.getChildren()) {
if (node.isElement()) {
DOMElement element = (DOMElement) node;
CatalogEntry catalogEntry = createCatalogEntry(baseURI, element);
if (catalogEntry != null) {
entries.add(catalogEntry);
}
}
}
return entries;
}
/**
* Returns a catalog entry for the given element and null if a catalog entry
* can't be made
*
* @param baseURI the base URI of the catalog entry
* @param element the element to turn into a catalog entry
* @return a catalog entry for the given element and null if a catalog entry
* can't be made
*/
private static CatalogEntry createCatalogEntry(@NonNull String baseURI, DOMElement element) {
if (isCatalogEntryWithURI(element)) {
return new URICatalogEntry(baseURI, element);
} else if (isCatalogEntryWithCatalog(element)) {
return new CatalogCatalogEntry(baseURI, element);
}
return null;
}
}
@@ -1,57 +0,0 @@
/*******************************************************************************
* Copyright (c) 2020 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
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx.extensions.catalog;
import java.nio.file.InvalidPathException;
import java.nio.file.Paths;
import org.eclipse.lemminx.dom.DOMAttr;
import org.eclipse.lemminx.dom.DOMElement;
import org.eclipse.lemminx.dom.DOMRange;
import org.eclipse.lemminx.utils.StringUtils;
import org.eclipse.lsp4j.jsonrpc.validation.NonNull;
/**
* Represents a catalog entry that uses the "uri" attribute to reference an external document
*/
public class URICatalogEntry extends CatalogEntry {
public URICatalogEntry(@NonNull String baseURI, DOMElement entryElement) {
super(baseURI, entryElement);
}
@Override
public DOMRange getLinkRange() {
DOMAttr uriAttr = CatalogUtils.getCatalogEntryURI(getEntryElement());
if (uriAttr == null) {
return null;
}
return uriAttr.getNodeAttrValue();
}
@Override
public String getResolvedURI() {
DOMAttr uriAttr = CatalogUtils.getCatalogEntryURI(getEntryElement());
if (uriAttr == null) {
return null;
}
String lastSegment = uriAttr.getValue();
if (StringUtils.isBlank(lastSegment)) {
return null;
}
try {
return Paths.get(getBaseURI(), lastSegment).toString();
} catch (InvalidPathException e) {
// See issue #977
return null;
}
}
}
@@ -1,82 +0,0 @@
/*******************************************************************************
* Copyright (c) 2020 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
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx.extensions.catalog;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.xerces.impl.XMLEntityManager;
import org.apache.xerces.util.URI.MalformedURIException;
import org.eclipse.lemminx.commons.BadLocationException;
import org.eclipse.lemminx.dom.DOMDocument;
import org.eclipse.lemminx.services.extensions.IDocumentLinkParticipant;
import org.eclipse.lemminx.utils.FilesUtils;
import org.eclipse.lemminx.utils.StringUtils;
import org.eclipse.lemminx.utils.XMLPositionUtility;
import org.eclipse.lsp4j.DocumentLink;
/**
* Document links that are specific to catalogs
*/
public class XMLCatalogDocumentLinkParticipant implements IDocumentLinkParticipant {
private static Logger LOGGER = Logger.getLogger(XMLCatalogDocumentLinkParticipant.class.getName());
@Override
public void findDocumentLinks(DOMDocument document, List<DocumentLink> links) {
for (CatalogEntry catalogEntry : CatalogUtils.getCatalogEntries(document)) {
DocumentLink docLink = createDocumentLinkFromCatalogEntry(document, catalogEntry);
if (docLink != null) {
links.add(docLink);
}
}
}
/**
* Return a DocumentLink for the given CatalogEntry or null if this fails
*
* @param document The document that the attribute belongs to
* @param catalogEntry The CatalogEntry that should be turned into a
* DocumentLink
* @return a document link that links the CatalogEntry to the external document
* it references or null otherwise
*/
private static DocumentLink createDocumentLinkFromCatalogEntry(DOMDocument document, CatalogEntry catalogEntry) {
try {
String path = getResolvedLocation(FilesUtils.removeFileScheme(document.getDocumentURI()),
catalogEntry.getResolvedURI());
if (path != null && catalogEntry.getLinkRange() != null) {
return XMLPositionUtility.createDocumentLink(catalogEntry.getLinkRange(), path, true);
}
} catch (BadLocationException e) {
LOGGER.log(Level.SEVERE, "Creation of document link failed", e);
}
return null;
}
/**
* Returns the expanded system location
*
* @return the expanded system location
*/
private static String getResolvedLocation(String documentURI, String location) {
if (StringUtils.isBlank(location)) {
return null;
}
try {
return XMLEntityManager.expandSystemId(location, documentURI, false);
} catch (MalformedURIException e) {
return location;
}
}
}
@@ -1,86 +0,0 @@
/**
* Copyright (c) 2018 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
* http://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
*/
package org.eclipse.lemminx.extensions.catalog;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.Set;
import java.util.stream.Collectors;
import org.eclipse.lemminx.client.InvalidPathWarner;
import org.eclipse.lemminx.client.PathFeature;
import org.eclipse.lemminx.extensions.contentmodel.settings.ContentModelSettings;
import org.eclipse.lemminx.services.IXMLNotificationService;
import org.eclipse.lemminx.services.extensions.IDocumentLinkParticipant;
import org.eclipse.lemminx.services.extensions.IXMLExtension;
import org.eclipse.lemminx.services.extensions.XMLExtensionsRegistry;
import org.eclipse.lemminx.services.extensions.save.ISaveContext;
import org.eclipse.lemminx.utils.FilesUtils;
import org.eclipse.lsp4j.InitializeParams;
/**
* XMl Catalog plugin.
*/
public class XMLCatalogPlugin implements IXMLExtension {
private XMLCatalogURIResolverExtension uiResolver;
private final IDocumentLinkParticipant documentLinkParticipant;
private InvalidPathWarner pathWarner;
public XMLCatalogPlugin() {
documentLinkParticipant = new XMLCatalogDocumentLinkParticipant();
}
@Override
public void doSave(ISaveContext context) {
Object initializationOptionsSettings = context.getSettings();
ContentModelSettings cmSettings = ContentModelSettings.getContentModelXMLSettings(initializationOptionsSettings);
if (cmSettings == null) {
return;
}
validateCatalogPaths(cmSettings);
}
@Override
public void start(InitializeParams params, XMLExtensionsRegistry registry) {
uiResolver = new XMLCatalogURIResolverExtension(registry);
registry.getResolverExtensionManager().registerResolver(uiResolver);
IXMLNotificationService notificationService = registry.getNotificationService();
if (notificationService != null) {
this.pathWarner = new InvalidPathWarner(notificationService);
}
registry.registerDocumentLinkParticipant(documentLinkParticipant);
}
@Override
public void stop(XMLExtensionsRegistry registry) {
registry.getResolverExtensionManager().unregisterResolver(uiResolver);
}
private void validateCatalogPaths(ContentModelSettings cmSettings) {
if (this.pathWarner == null) {
return; // happen when notification service is not available
}
String[] catalogs = cmSettings.getCatalogs();
Set<String> invalidCatalogs = Arrays.stream(catalogs).filter(c -> {
return Files.notExists(FilesUtils.getPath(c));
}).collect(Collectors.toSet());
if (invalidCatalogs.size() > 0) {
this.pathWarner.onInvalidFilePath(invalidCatalogs, PathFeature.CATALOGS);
} else {
this.pathWarner.evictKey(PathFeature.CATALOGS);
}
}
}
@@ -1,92 +0,0 @@
/**
* Copyright (c) 2018 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
* http://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
*/
package org.eclipse.lemminx.extensions.catalog;
import java.io.IOException;
import org.apache.xerces.xni.XMLResourceIdentifier;
import org.apache.xerces.xni.XNIException;
import org.apache.xerces.xni.parser.XMLInputSource;
import org.eclipse.lemminx.dom.DOMDocument;
import org.eclipse.lemminx.services.extensions.XMLExtensionsRegistry;
import org.eclipse.lemminx.uriresolver.CacheResourcesManager;
import org.eclipse.lemminx.uriresolver.CacheResourcesManager.ResourceToDeploy;
import org.eclipse.lemminx.uriresolver.URIResolverExtension;
/**
* Resolve the XML Schema of XML/Catalog
*
*/
public class XMLCatalogURIResolverExtension implements URIResolverExtension {
/**
* The XML Catalog namespace URI (=
* http://www.oasis-open.org/committees/entity/release/1.1/catalog.xsd)
*/
private static final String CATALOG_NAMESPACE_URI = "urn:oasis:names:tc:entity:xmlns:xml:catalog"; //$NON-NLS-1$
private static final String CATALOG_SYSTEM = "http://www.oasis-open.org/committees/entity/release/1.1/catalog.xsd";
private static final ResourceToDeploy CATALOG_RESOURCE = new ResourceToDeploy(CATALOG_SYSTEM,
"schemas/catalog/catalog-1.1.xsd");
private final XMLExtensionsRegistry extensionsRegistry;
@Override
public String getName() {
return "embedded catalog.xsd";
}
public XMLCatalogURIResolverExtension(XMLExtensionsRegistry extensionsRegistry) {
this.extensionsRegistry = extensionsRegistry;
}
@Override
public String resolve(String baseLocation, String publicId, String systemId) {
if (!CATALOG_NAMESPACE_URI.equals(publicId)) {
return null;
}
if (hasDTDorXMLSchema(baseLocation)) {
return null;
}
try {
return CacheResourcesManager.getResourceCachePath(CATALOG_RESOURCE).toFile().toURI().toString();
} catch (Exception e) {
// Do nothing?
}
return CATALOG_SYSTEM;
}
@Override
public XMLInputSource resolveEntity(XMLResourceIdentifier resourceIdentifier) throws XNIException, IOException {
if (hasDTDorXMLSchema(resourceIdentifier.getBaseSystemId())) {
return null;
}
String publicId = resourceIdentifier.getNamespace();
if (CATALOG_NAMESPACE_URI.equals(publicId)) {
String baseLocation = resourceIdentifier.getBaseSystemId();
String catalogFilePath = resolve(baseLocation, publicId, null);
if (catalogFilePath != null) {
return new XMLInputSource(publicId, catalogFilePath, catalogFilePath);
}
}
return null;
}
private boolean hasDTDorXMLSchema(String uri) {
DOMDocument document = extensionsRegistry.getDocumentProvider().getDocument(uri);
if (document == null) {
return false;
}
return document.hasDTD() || document.hasSchemaLocation() || document.hasNoNamespaceSchemaLocation();
}
}
@@ -1,243 +0,0 @@
/**
* Copyright (c) 2018 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
* http://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
*/
package org.eclipse.lemminx.extensions.contentmodel;
import java.util.Objects;
import org.eclipse.lemminx.dom.DOMDocument;
import org.eclipse.lemminx.extensions.contentmodel.commands.AssociateGrammarCommand;
import org.eclipse.lemminx.extensions.contentmodel.commands.CheckBoundGrammarCommand;
import org.eclipse.lemminx.extensions.contentmodel.commands.CheckFilePatternCommand;
import org.eclipse.lemminx.extensions.contentmodel.commands.XMLValidationAllFilesCommand;
import org.eclipse.lemminx.extensions.contentmodel.commands.XMLValidationFileCommand;
import org.eclipse.lemminx.extensions.contentmodel.model.ContentModelManager;
import org.eclipse.lemminx.extensions.contentmodel.participants.ContentModelCodeActionParticipant;
import org.eclipse.lemminx.extensions.contentmodel.participants.ContentModelCodeLensParticipant;
import org.eclipse.lemminx.extensions.contentmodel.participants.ContentModelCompletionParticipant;
import org.eclipse.lemminx.extensions.contentmodel.participants.ContentModelDocumentLinkParticipant;
import org.eclipse.lemminx.extensions.contentmodel.participants.ContentModelHoverParticipant;
import org.eclipse.lemminx.extensions.contentmodel.participants.ContentModelSymbolsProviderParticipant;
import org.eclipse.lemminx.extensions.contentmodel.participants.ContentModelTypeDefinitionParticipant;
import org.eclipse.lemminx.extensions.contentmodel.participants.DocumentTelemetryParticipant;
import org.eclipse.lemminx.extensions.contentmodel.participants.diagnostics.ContentModelDiagnosticsParticipant;
import org.eclipse.lemminx.extensions.contentmodel.settings.ContentModelSettings;
import org.eclipse.lemminx.extensions.contentmodel.settings.XMLValidationSettings;
import org.eclipse.lemminx.services.IXMLDocumentProvider;
import org.eclipse.lemminx.services.IXMLValidationService;
import org.eclipse.lemminx.services.extensions.ICodeActionParticipant;
import org.eclipse.lemminx.services.extensions.ICompletionParticipant;
import org.eclipse.lemminx.services.extensions.IDocumentLinkParticipant;
import org.eclipse.lemminx.services.extensions.IHoverParticipant;
import org.eclipse.lemminx.services.extensions.ITypeDefinitionParticipant;
import org.eclipse.lemminx.services.extensions.IXMLExtension;
import org.eclipse.lemminx.services.extensions.XMLExtensionsRegistry;
import org.eclipse.lemminx.services.extensions.codelens.ICodeLensParticipant;
import org.eclipse.lemminx.services.extensions.commands.IXMLCommandService;
import org.eclipse.lemminx.services.extensions.diagnostics.IDiagnosticsParticipant;
import org.eclipse.lemminx.services.extensions.save.ISaveContext;
import org.eclipse.lemminx.uriresolver.URIResolverExtensionManager;
import org.eclipse.lemminx.utils.DOMUtils;
import org.eclipse.lsp4j.InitializeParams;
/**
* Content model plugin extension to provide:
*
* <ul>
* <li>completion based on XML Schema, DTD...</li>
* <li>hover based on XML Schema</li>
* <li>diagnostics based on on XML Schema, DTD...</li>
* </ul>
*/
public class ContentModelPlugin implements IXMLExtension {
private final ICompletionParticipant completionParticipant;
private final IHoverParticipant hoverParticipant;
private final IDiagnosticsParticipant diagnosticsParticipant;
private final ICodeActionParticipant codeActionParticipant;
private IDocumentLinkParticipant documentLinkParticipant;
private final ITypeDefinitionParticipant typeDefinitionParticipant;
private ContentModelSymbolsProviderParticipant symbolsProviderParticipant;
private ICodeLensParticipant codeLensParticipant;
ContentModelManager contentModelManager;
private ContentModelSettings cmSettings;
private XMLValidationSettings currentValidationSettings;
private DocumentTelemetryParticipant documentTelemetryParticipant;
public ContentModelPlugin() {
completionParticipant = new ContentModelCompletionParticipant();
hoverParticipant = new ContentModelHoverParticipant();
diagnosticsParticipant = new ContentModelDiagnosticsParticipant(this);
codeActionParticipant = new ContentModelCodeActionParticipant();
typeDefinitionParticipant = new ContentModelTypeDefinitionParticipant();
}
@Override
public void doSave(ISaveContext context) {
if (context.getType() == ISaveContext.SaveContextType.DOCUMENT) {
// The save is done for a given XML file
String documentURI = context.getUri();
DOMDocument document = context.getDocument(documentURI);
if (document != null && DOMUtils.isCatalog(document)) {
// the XML document which has changed is a XML catalog.
// 1) refresh catalogs
contentModelManager.refreshCatalogs();
}
// 2) Validate all opened XML files except the catalog which have changed
context.collectDocumentToValidate(d -> {
DOMDocument xml = context.getDocument(d.getDocumentURI());
xml.resetGrammar();
return !documentURI.equals(d.getDocumentURI());
});
} else {
// Settings
updateSettings(context);
}
}
private void updateSettings(ISaveContext saveContext) {
Object initializationOptionsSettings = saveContext.getSettings();
cmSettings = ContentModelSettings.getContentModelXMLSettings(initializationOptionsSettings);
if (cmSettings != null) {
updateSettings(cmSettings, saveContext);
} else {
currentValidationSettings = null;
}
}
private void updateSettings(ContentModelSettings settings, ISaveContext context) {
if (settings.getCatalogs() != null) {
// Update XML catalog settings
boolean catalogPathsChanged = contentModelManager.setCatalogs(settings.getCatalogs());
if (catalogPathsChanged) {
// Validate all opened XML files
context.collectDocumentToValidate(d -> {
DOMDocument xml = context.getDocument(d.getDocumentURI());
if (xml == null) {
return false;
}
xml.resetGrammar();
return true;
});
}
}
if (settings.getFileAssociations() != null) {
// Update XML file associations
boolean fileAssociationsChanged = contentModelManager.setFileAssociations(settings.getFileAssociations());
if (fileAssociationsChanged) {
// Validate all opened XML files
context.collectDocumentToValidate(d -> {
DOMDocument xml = context.getDocument(d.getDocumentURI());
xml.resetGrammar();
return true;
});
}
}
// Update use cache, only if it is set in the settings.
Boolean useCache = settings.isUseCache();
if (useCache != null) {
contentModelManager.setUseCache(useCache);
}
// Update symbols
boolean showReferencedGrammars = settings.isShowReferencedGrammars();
symbolsProviderParticipant.setEnabled(showReferencedGrammars);
// Track if validation settings has changed
XMLValidationSettings oldValidationSettings = currentValidationSettings;
currentValidationSettings = cmSettings.getValidation();
if (oldValidationSettings != null && !Objects.equals(oldValidationSettings, currentValidationSettings)) {
context.collectDocumentToValidate(d -> true);
}
}
@Override
public void start(InitializeParams params, XMLExtensionsRegistry registry) {
URIResolverExtensionManager resolverManager = registry.getComponent(URIResolverExtensionManager.class);
contentModelManager = new ContentModelManager(resolverManager);
registry.registerComponent(contentModelManager);
if (params != null) {
contentModelManager.setRootURI(params.getRootUri());
}
documentLinkParticipant = new ContentModelDocumentLinkParticipant(resolverManager);
registry.registerCompletionParticipant(completionParticipant);
registry.registerHoverParticipant(hoverParticipant);
registry.registerDiagnosticsParticipant(diagnosticsParticipant);
registry.registerCodeActionParticipant(codeActionParticipant);
registry.registerDocumentLinkParticipant(documentLinkParticipant);
registry.registerTypeDefinitionParticipant(typeDefinitionParticipant);
symbolsProviderParticipant = new ContentModelSymbolsProviderParticipant(contentModelManager);
registry.registerSymbolsProviderParticipant(symbolsProviderParticipant);
codeLensParticipant = new ContentModelCodeLensParticipant(contentModelManager);
registry.registerCodeLensParticipant(codeLensParticipant);
documentTelemetryParticipant = new DocumentTelemetryParticipant(registry.getTelemetryManager(), contentModelManager);
registry.registerDocumentLifecycleParticipant(documentTelemetryParticipant);
// Register custom commands to re-validate XML files
IXMLCommandService commandService = registry.getCommandService();
if (commandService != null) {
IXMLValidationService validationService = registry.getValidationService();
IXMLDocumentProvider documentProvider = registry.getDocumentProvider();
commandService.registerCommand(XMLValidationFileCommand.COMMAND_ID,
new XMLValidationFileCommand(contentModelManager, documentProvider, validationService));
commandService.registerCommand(XMLValidationAllFilesCommand.COMMAND_ID,
new XMLValidationAllFilesCommand(contentModelManager, documentProvider, validationService));
commandService.registerCommand(AssociateGrammarCommand.COMMAND_ID,
new AssociateGrammarCommand(documentProvider));
commandService.registerCommand(CheckBoundGrammarCommand.COMMAND_ID,
new CheckBoundGrammarCommand(documentProvider));
commandService.registerCommand(CheckFilePatternCommand.COMMAND_ID,
new CheckFilePatternCommand());
}
}
@Override
public void stop(XMLExtensionsRegistry registry) {
registry.unregisterCompletionParticipant(completionParticipant);
registry.unregisterHoverParticipant(hoverParticipant);
registry.unregisterDiagnosticsParticipant(diagnosticsParticipant);
registry.unregisterCodeActionParticipant(codeActionParticipant);
registry.unregisterDocumentLinkParticipant(documentLinkParticipant);
registry.unregisterTypeDefinitionParticipant(typeDefinitionParticipant);
registry.unregisterSymbolsProviderParticipant(symbolsProviderParticipant);
registry.unregisterCodeLensParticipant(codeLensParticipant);
registry.unregisterDocumentLifecycleParticipant(documentTelemetryParticipant);
// Un-register custom commands to re-validate XML files
IXMLCommandService commandService = registry.getCommandService();
if (commandService != null) {
commandService.unregisterCommand(XMLValidationFileCommand.COMMAND_ID);
commandService.unregisterCommand(XMLValidationAllFilesCommand.COMMAND_ID);
commandService.unregisterCommand(AssociateGrammarCommand.COMMAND_ID);
commandService.unregisterCommand(CheckBoundGrammarCommand.COMMAND_ID);
commandService.unregisterCommand(CheckFilePatternCommand.COMMAND_ID);
}
}
public ContentModelSettings getContentModelSettings() {
return cmSettings;
}
public ContentModelManager getContentModelManager() {
return contentModelManager;
}
}

Some files were not shown because too many files have changed in this diff Show More