mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-16 07:49:44 +08:00
Removed usage of LemMinX
Enhanced XML Auto complete
This commit is contained in:
+13
-11
@@ -1,7 +1,10 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
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 {
|
||||
compileSdkVersion rootProject.ext.compileSdk
|
||||
@@ -61,13 +64,11 @@ android {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
signingConfig signingConfigs.debug
|
||||
stringfog.enable false
|
||||
}
|
||||
release {
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
signingConfig signingConfigs.release
|
||||
stringfog.enable true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,10 +80,12 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
stringfog {
|
||||
key 'WVV0aGMwaEFZVzVFVW05SlJFbGtaUT09'
|
||||
enable true
|
||||
implementation 'com.github.megatronking.stringfog.xor.StringFogImpl'
|
||||
if(rootProject.ext.stringfogEnabled) {
|
||||
stringfog {
|
||||
key 'WVV0aGMwaEFZVzVFVW05SlJFbGtaUT09'
|
||||
enable true
|
||||
implementation 'com.github.megatronking.stringfog.xor.StringFogImpl'
|
||||
}
|
||||
}
|
||||
|
||||
task copyBindings(type: Copy) {
|
||||
@@ -114,7 +117,7 @@ dependencies {
|
||||
implementation 'androidx.annotation:annotation:1.2.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.3.1'
|
||||
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.drawerlayout:drawerlayout:1.1.1'
|
||||
implementation 'androidx.gridlayout:gridlayout:1.0.0'
|
||||
@@ -126,7 +129,7 @@ dependencies {
|
||||
implementation 'com.google.android.material:material:1.4.0'
|
||||
|
||||
// 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-crashlytics'
|
||||
implementation 'com.google.firebase:firebase-messaging'
|
||||
@@ -137,8 +140,7 @@ dependencies {
|
||||
implementation project(':emulator')
|
||||
implementation project(':emulatorview')
|
||||
implementation project(':javapoet')
|
||||
|
||||
api project(':lemminx')
|
||||
implementation project(':lsp')
|
||||
|
||||
// LSP4J
|
||||
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());
|
||||
LSP.Java.initialized();
|
||||
});
|
||||
LSP.XML.start(() -> {
|
||||
Optional<InitializeResult> result = LSP.XML.init(mProject.getProjectPath());
|
||||
LSP.XML.initialized();
|
||||
});
|
||||
}
|
||||
|
||||
private void getProjectFromIntent() {
|
||||
|
||||
@@ -285,7 +285,7 @@ public class EditorFragment extends BaseFragment implements EditorEventListener
|
||||
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
|
||||
mBinding.editor.setFile(getFile());
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ public class GroovyAutoComplete implements AutoCompleteProvider {
|
||||
private static final List<String> OTHERS = createOtherCompletions();
|
||||
|
||||
@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<>();
|
||||
|
||||
for(String artifact : ANDROIDX_ARTIFACTS) {
|
||||
|
||||
@@ -22,7 +22,7 @@ public class JavaAutoComplete implements AutoCompleteProvider {
|
||||
private CompletableFuture<Either<List<CompletionItem>, CompletionList>> future;
|
||||
|
||||
@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);
|
||||
if(languageServer != null && fileUri != null) {
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@ public class LogLanguageImpl extends BaseLanguage {
|
||||
private static class LogCompletor implements AutoCompleteProvider {
|
||||
|
||||
@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>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,72 +1,34 @@
|
||||
package com.itsaky.androidide.language.xml;
|
||||
|
||||
import com.itsaky.androidide.lsp.LSPProvider;
|
||||
import com.itsaky.lsp.services.IDELanguageServer;
|
||||
import com.itsaky.androidide.app.StudioApp;
|
||||
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.text.TextAnalyzeResult;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
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 {
|
||||
|
||||
|
||||
private static final Logger LOG = Logger.instance("XMLAutoComplete");
|
||||
|
||||
private Comparator<CompletionItem> RESULTS_SORTER = new Comparator<CompletionItem>(){
|
||||
|
||||
@Override
|
||||
public int compare(CompletionItem p1, CompletionItem p2) {
|
||||
if(p1.getSortText() == null && p1.getSortText() != null) return -1;
|
||||
if(p1.getSortText() != null && p1.getSortText() == null) return 1;
|
||||
if(p1.getSortText() == null && p1.getSortText() == null) return 0;
|
||||
if(p1.getSortText() == null && p2.getSortText() != null) return -1;
|
||||
if(p1.getSortText() != null && p2.getSortText() == null) return 1;
|
||||
if(p1.getSortText() == null && p2.getSortText() == null) return 0;
|
||||
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
|
||||
public List<CompletionItem> getAutoCompleteItems(String fileUri, String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int index, int line, int column) {
|
||||
final IDELanguageServer server = LSPProvider.getServerForLanguage(LSPProvider.LANGUAGE_XML);
|
||||
if(server == null)
|
||||
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();
|
||||
public List<CompletionItem> getAutoCompleteItems(CharSequence content, String fileUri, String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int index, int line, int column) {
|
||||
final XMLCompletionService service = StudioApp.getInstance().getXmlCompletionService();
|
||||
return sort(service.complete(content, index, line, column, prefix.toLowerCase().trim()));
|
||||
}
|
||||
|
||||
private List<CompletionItem> sort(List<CompletionItem> result) {
|
||||
|
||||
@@ -14,13 +14,14 @@ import java.io.File;
|
||||
import java.io.StringReader;
|
||||
import org.antlr.v4.runtime.CharStreams;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import com.itsaky.androidide.lsp.LanguageServerWrapper;
|
||||
|
||||
public class XMLLanguage extends BaseLanguage {
|
||||
|
||||
private XMLAnalyzer analyzer;
|
||||
private XMLAutoComplete completer;
|
||||
private NewlineHandler[] newlineHandlers = new NewlineHandler[] {};
|
||||
|
||||
|
||||
public XMLLanguage() {
|
||||
super(null);
|
||||
}
|
||||
@@ -34,7 +35,7 @@ public class XMLLanguage extends BaseLanguage {
|
||||
|
||||
@Override
|
||||
public IDELanguageServer getLanguageServer() {
|
||||
return LSPProvider.getServerForLanguage(getLanguageCode());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+92
-26
@@ -3,7 +3,6 @@ package com.itsaky.androidide.language.xml.completion;
|
||||
import com.itsaky.androidide.language.xml.lexer.XMLLexer;
|
||||
import com.itsaky.androidide.utils.Environment;
|
||||
import com.itsaky.androidide.utils.FileUtil;
|
||||
import io.github.rosemoe.editor.widget.CodeEditor;
|
||||
import java.io.File;
|
||||
import java.io.StringReader;
|
||||
import java.util.ArrayList;
|
||||
@@ -17,16 +16,16 @@ import java.util.Set;
|
||||
import org.antlr.v4.runtime.CharStreams;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.eclipse.lsp4j.CompletionItem;
|
||||
import org.eclipse.lsp4j.CompletionItemKind;
|
||||
import org.eclipse.lsp4j.InsertTextFormat;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.jsoup.nodes.Element;
|
||||
import org.jsoup.select.Elements;
|
||||
import org.eclipse.lsp4j.InsertTextFormat;
|
||||
import org.eclipse.lsp4j.CompletionItemKind;
|
||||
|
||||
public class XMLCompletionService {
|
||||
private HashMap<String, Attr> attrs;
|
||||
private List<String> widgets;
|
||||
private List<Widget> widgets;
|
||||
private final File ANDROID_ATTRS;
|
||||
private final File ANDROID_WIDGETS;
|
||||
|
||||
@@ -37,6 +36,7 @@ public class XMLCompletionService {
|
||||
this.ANDROID_WIDGETS = new File(Environment.BOOTCLASSPATH.getParentFile(), "data/widgets.txt");
|
||||
this.attrs = new HashMap<>();
|
||||
this.widgets = new ArrayList<>();
|
||||
|
||||
if(ANDROID_ATTRS.exists() && ANDROID_ATTRS.isFile()) {
|
||||
addAttributesFrom(ANDROID_ATTRS, true);
|
||||
}
|
||||
@@ -51,8 +51,7 @@ public class XMLCompletionService {
|
||||
return initiated;
|
||||
}
|
||||
|
||||
|
||||
public List<CompletionItem> complete(int index, int line, int column, String prefix) {
|
||||
public List<CompletionItem> complete(CharSequence content, int index, int line, int column, String prefix) {
|
||||
final List<CompletionItem> result = new ArrayList<>();
|
||||
if(prefix.startsWith("<") || prefix.startsWith("</")) {
|
||||
boolean slash = false;
|
||||
@@ -61,16 +60,12 @@ public class XMLCompletionService {
|
||||
slash = true;
|
||||
prefix = prefix.substring(1);
|
||||
}
|
||||
for(String name : widgets) {
|
||||
if(simpleName(name).toLowerCase(Locale.US).startsWith(prefix))
|
||||
result.add(widgetNameAsCompletion(name, slash));
|
||||
for(Widget view : widgets) {
|
||||
if(view.simpleName.toLowerCase(Locale.US).startsWith(prefix))
|
||||
result.add(widgetNameAsCompletion(view, slash));
|
||||
}
|
||||
return handleResults(result);
|
||||
} else {
|
||||
/**
|
||||
* TODO: Change to LSP
|
||||
*/
|
||||
final String content = "";
|
||||
final IsInValueScanner scanner = new IsInValueScanner(index);
|
||||
final String name = scanner.scan(content);
|
||||
if(name != null) {
|
||||
@@ -110,24 +105,64 @@ public class XMLCompletionService {
|
||||
CompletionItem item = new CompletionItem();
|
||||
item.setLabel(attr.name);
|
||||
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.setSortText("1" + attr.name);
|
||||
item.setKind(CompletionItemKind.Snippet);
|
||||
return item;
|
||||
}
|
||||
|
||||
private CompletionItem widgetNameAsCompletion(String name, boolean slash) {
|
||||
final String n = simpleName(name);
|
||||
private String createAttributeInsertText(Attr attr) {
|
||||
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();
|
||||
item.setLabel(n);
|
||||
item.setDetail(name);
|
||||
item.setInsertText("<" + (slash ? "/" : "") + n);
|
||||
item.setLabel(view.simpleName);
|
||||
item.setDetail(view.name);
|
||||
item.setInsertText(createTagInsertText(view, slash));
|
||||
item.setInsertTextFormat(InsertTextFormat.PlainText);
|
||||
item.setSortText("2" + n);
|
||||
item.setSortText("2" + view.simpleName);
|
||||
item.setKind(CompletionItemKind.Value);
|
||||
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) {
|
||||
return name.substring(name.lastIndexOf(".") + 1);
|
||||
@@ -142,9 +177,19 @@ public class XMLCompletionService {
|
||||
if(read != null && read.length() > 0) {
|
||||
widgets.clear();
|
||||
for(String line : read.split("\\n")) {
|
||||
String name = line.split("\\s")[0];
|
||||
name = name.substring(1);
|
||||
widgets.add(name);
|
||||
final String name = line.split("\\s")[0];
|
||||
if(name == null || name.trim().isEmpty()) continue;
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -199,9 +244,9 @@ public class XMLCompletionService {
|
||||
return start <= cursorIndex && cursorIndex <= end;
|
||||
}
|
||||
|
||||
public String scan(String xmlContent) {
|
||||
public String scan(CharSequence xmlContent) {
|
||||
try {
|
||||
XMLLexer lexer = new XMLLexer(CharStreams.fromReader(new StringReader(xmlContent)));
|
||||
XMLLexer lexer = new XMLLexer(CharStreams.fromReader(new StringReader(xmlContent.toString())));
|
||||
Token token;
|
||||
String attrName = null;
|
||||
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 name;
|
||||
|
||||
@@ -240,4 +285,25 @@ public class XMLCompletionService {
|
||||
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.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.lsp.services.IDELanguageServer;
|
||||
import java.io.File;
|
||||
@@ -40,13 +38,7 @@ public class LSP {
|
||||
public static EditorActivityProvider PROVIDER;
|
||||
private static final Logger LOG = Logger.instance("LSP");
|
||||
|
||||
public static LSPHandler Java = new JLSHandler();
|
||||
public static LSPHandler XML = new XMLLanguageServerHandler();
|
||||
|
||||
public static void startAll(Runnable onStarted) {
|
||||
LSP.Java.start(onStarted);
|
||||
LSP.XML.start(onStarted);
|
||||
}
|
||||
public static JLSHandler Java = new JLSHandler();
|
||||
|
||||
/**
|
||||
* Stores information about ports at which the specified language server must be started
|
||||
@@ -142,7 +134,6 @@ public class LSP {
|
||||
public static void shutdownAll() {
|
||||
// Shutdown language servers one by one
|
||||
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;
|
||||
|
||||
import java.util.Optional;
|
||||
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
|
||||
* @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 {
|
||||
|
||||
@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>();
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ import org.eclipse.lsp4j.Position;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
import org.eclipse.lsp4j.TextEdit;
|
||||
import org.eclipse.lsp4j.Command;
|
||||
import io.github.rosemoe.editor.text.ContentLine;
|
||||
|
||||
public class EditorAutoCompleteWindow extends EditorBasePopupWindow {
|
||||
private final static String TIP = "Refreshing...";
|
||||
@@ -187,12 +188,11 @@ public class EditorAutoCompleteWindow extends EditorBasePopupWindow {
|
||||
* @param pos Index of auto complete item
|
||||
*/
|
||||
public void select(int pos) {
|
||||
CompletionItem item = ((EditorCompletionAdapter) mBinding.list.getAdapter()).getItem(pos);
|
||||
Range range = getIdentifierRange(mEditor.getCursor().getRight(), mEditor.getText());
|
||||
final CompletionItem item = ((EditorCompletionAdapter) mBinding.list.getAdapter()).getItem(pos);
|
||||
final Range range = getIdentifierRange(mEditor.getCursor().getRight(), mEditor.getText());
|
||||
|
||||
String text = item.getInsertText() == null ? item.getLabel() : item.getInsertText();
|
||||
final boolean shiftLeft = text.contains("$0");
|
||||
final boolean atEnd = text.endsWith("$0");
|
||||
text = text.replace("$0", "");
|
||||
final boolean shiftCursor = item.getInsertText() != null && text.contains("$0");
|
||||
|
||||
mEditor.getText().delete(
|
||||
range.getStart().getLine(),
|
||||
@@ -202,8 +202,14 @@ public class EditorAutoCompleteWindow extends EditorBasePopupWindow {
|
||||
);
|
||||
mEditor.getCursor().onCommitText(text);
|
||||
|
||||
if(shiftLeft && !atEnd) {
|
||||
mEditor.moveSelectionLeft();
|
||||
if(shiftCursor) {
|
||||
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();
|
||||
@@ -226,7 +232,7 @@ public class EditorAutoCompleteWindow extends EditorBasePopupWindow {
|
||||
Command cmd = item.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
|
||||
// If the insert text contains '(', automatically trigger this action
|
||||
mEditor.signatureHelp("(");
|
||||
@@ -308,6 +314,7 @@ public class EditorAutoCompleteWindow extends EditorBasePopupWindow {
|
||||
private final long mTime;
|
||||
private final String mPrefix;
|
||||
private final String mFileUri;
|
||||
private final CharSequence mContent;
|
||||
private final boolean mInner;
|
||||
private final TextAnalyzeResult mColors;
|
||||
private final int mIndex;
|
||||
@@ -319,6 +326,7 @@ public class EditorAutoCompleteWindow extends EditorBasePopupWindow {
|
||||
mTime = requestTime;
|
||||
mPrefix = prefix;
|
||||
mFileUri = mEditor.getFile() != null ? mEditor.getFile().toURI().toString() : null;
|
||||
mContent = mEditor.getText();
|
||||
mColors = mEditor.getTextAnalyzeResult();
|
||||
mIndex = mEditor.getCursor().getLeft();
|
||||
mLine = mEditor.getCursor().getLeftLine();
|
||||
@@ -329,7 +337,7 @@ public class EditorAutoCompleteWindow extends EditorBasePopupWindow {
|
||||
@Override
|
||||
public void run() {
|
||||
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) {
|
||||
e.printStackTrace();
|
||||
displayResults(new ArrayList<>(), mTime);
|
||||
|
||||
@@ -13,6 +13,8 @@ buildscript {
|
||||
|
||||
javaSourceVersion = JavaVersion.VERSION_1_8
|
||||
javaTargetVersion = JavaVersion.VERSION_1_8
|
||||
|
||||
stringfogEnabled = false
|
||||
}
|
||||
|
||||
repositories {
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# 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.
|
||||
# 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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
-38
@@ -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;
|
||||
}
|
||||
}
|
||||
-33
@@ -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>
|
||||
* <?xml-model href="http://www.docbook.org/xml/5.0/xsd/docbook.xsd"?>
|
||||
* </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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
-82
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
-92
@@ -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();
|
||||
}
|
||||
}
|
||||
-243
@@ -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
Reference in New Issue
Block a user