diff --git a/org.eclipse.lemminx/src/main/java/com/thaiopensource/relaxng/pattern/CMRelaxNGDocument.java b/org.eclipse.lemminx/src/main/java/com/thaiopensource/relaxng/pattern/CMRelaxNGDocument.java index 7441050f2..c077453b8 100644 --- a/org.eclipse.lemminx/src/main/java/com/thaiopensource/relaxng/pattern/CMRelaxNGDocument.java +++ b/org.eclipse.lemminx/src/main/java/com/thaiopensource/relaxng/pattern/CMRelaxNGDocument.java @@ -317,6 +317,6 @@ DOMNode findNodeAt(Locator locator) { private static String getTextContent(DOMElement element) { int start = element.getStartTagCloseOffset() + 1; int end = element.getEndTagOpenOffset(); - return element.getOwnerDocument().getText().substring(start, end); + return element.getOwnerDocument().getTextSequence().subSequence(start, end).toString(); } } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/CodeActionFactory.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/CodeActionFactory.java index 24363b3ed..dd0fd6793 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/CodeActionFactory.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/CodeActionFactory.java @@ -28,7 +28,6 @@ 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; @@ -49,7 +48,7 @@ public class CodeActionFactory { * @param diagnostic * @return */ - public static CodeAction remove(String title, Range range, TextDocumentItem document, Diagnostic diagnostic) { + public static CodeAction remove(String title, Range range, TextDocument document, Diagnostic diagnostic) { return replace(title, range, "", document, diagnostic); } @@ -64,7 +63,7 @@ public static CodeAction remove(String title, Range range, TextDocumentItem docu * * @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, + public static CodeAction insert(String title, Position position, String insertText, TextDocument document, Diagnostic diagnostic) { CodeAction insertContentAction = new CodeAction(title); insertContentAction.setKind(CodeActionKind.QuickFix); @@ -84,7 +83,7 @@ public static CodeAction insert(String title, Position position, String insertTe * * @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) { + public static TextDocumentEdit insertEdit(String insertText, Position position, TextDocument document) { TextEdit edit = insertEdit(insertText, position); return insertEdits(document, Collections.singletonList(edit)); } @@ -93,19 +92,19 @@ public static TextEdit insertEdit(String insertText, Position position) { return new TextEdit(new Range(position, position), insertText); } - public static TextDocumentEdit insertEdits(TextDocumentItem document, List edits) { + public static TextDocumentEdit insertEdits(TextDocument document, List 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, + public static CodeAction replace(String title, Range range, String replaceText, TextDocument document, Diagnostic diagnostic) { TextEdit replace = new TextEdit(range, replaceText); return replace(title, Collections.singletonList(replace), document, diagnostic); } - public static CodeAction replace(String title, List replace, TextDocumentItem document, + public static CodeAction replace(String title, List replace, TextDocument document, Diagnostic diagnostic) { CodeAction insertContentAction = new CodeAction(title); @@ -128,7 +127,7 @@ public static CodeAction replace(String title, List replace, TextDocum * @param document * @return the workspace edit of a given replacement text and range. */ - public static WorkspaceEdit getReplaceWorkspaceEdit(String replaceText, Range range, TextDocumentItem document) { + public static WorkspaceEdit getReplaceWorkspaceEdit(String replaceText, Range range, TextDocument document) { TextEdit replace = new TextEdit(range, replaceText); VersionedTextDocumentIdentifier versionedTextDocumentIdentifier = new VersionedTextDocumentIdentifier( document.getUri(), document.getVersion()); @@ -137,8 +136,8 @@ public static WorkspaceEdit getReplaceWorkspaceEdit(String replaceText, Range ra return new WorkspaceEdit(Collections.singletonList(Either.forLeft(textDocumentEdit))); } - public static CodeAction replaceAt(String title, String replaceText, TextDocumentItem document, - Diagnostic diagnostic, Collection ranges) { + public static CodeAction replaceAt(String title, String replaceText, TextDocument document, Diagnostic diagnostic, + Collection ranges) { CodeAction insertContentAction = new CodeAction(title); insertContentAction.setKind(CodeActionKind.QuickFix); insertContentAction.setDiagnostics(Arrays.asList(diagnostic)); diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/ILineTracker.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/ILineTracker.java index 455334bee..6a53c1416 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/ILineTracker.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/ILineTracker.java @@ -56,7 +56,7 @@ public interface ILineTracker { * @param text the text whose number of lines should be computed * @return the number of lines in the given text */ - int computeNumberOfLines(String text); + int computeNumberOfLines(CharSequence text); /** * Returns the number of lines. @@ -135,14 +135,14 @@ public interface ILineTracker { * @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; + void replace(int offset, int length, CharSequence text) throws BadLocationException; /** * Sets the tracked text to the specified text. * * @param text the new tracked text */ - void set(String text); + void set(CharSequence text); Position getPositionAt(int position) throws BadLocationException; diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/ListLineTracker.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/ListLineTracker.java index fe7639011..3520991c7 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/ListLineTracker.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/ListLineTracker.java @@ -304,7 +304,7 @@ public final int getNumberOfLines(int position, int length) throws BadLocationEx } @Override - public final int computeNumberOfLines(String text) { + public final int computeNumberOfLines(CharSequence text) { int count = 0; int start = 0; DelimiterInfo delimiterInfo = nextDelimiterInfo(text, start); @@ -343,7 +343,7 @@ public final String getLineDelimiter(int line) throws BadLocationException { * @param offset the offset in the given text * @return the information of the first found delimiter or null */ - protected DelimiterInfo nextDelimiterInfo(String text, int offset) { + protected DelimiterInfo nextDelimiterInfo(CharSequence text, int offset) { char ch; int length = text.length(); for (int i = offset; i < length; i++) { @@ -389,7 +389,7 @@ protected DelimiterInfo nextDelimiterInfo(String text, int offset) { * @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) { + private int createLines(CharSequence text, int insertPosition, int offset) { int count = 0; int start = 0; @@ -427,12 +427,12 @@ private int createLines(String text, int insertPosition, int offset) { } @Override - public final void replace(int position, int length, String text) throws BadLocationException { + public final void replace(int position, int length, CharSequence text) throws BadLocationException { throw new UnsupportedOperationException(); } @Override - public final void set(String text) { + public final void set(CharSequence text) { fLines.clear(); if (text != null) { fTextLength = text.length(); diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/ModelTextDocument.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/ModelTextDocument.java index 2e55e2d90..7c2de473e 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/ModelTextDocument.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/ModelTextDocument.java @@ -97,12 +97,12 @@ private synchronized T getSynchronizedModel() { return model; } - @Override + /*@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) { diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/TextDocument.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/TextDocument.java index e62a3db94..7b9aa4d8b 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/TextDocument.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/TextDocument.java @@ -17,19 +17,52 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.eclipse.lemminx.commons.text.CompositeCharSequence; +import org.eclipse.lemminx.commons.text.ImmutableCharSequence; +import org.eclipse.lemminx.commons.text.ImmutableCharSequenceImpl; import org.eclipse.lsp4j.Position; import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.TextDocumentContentChangeEvent; import org.eclipse.lsp4j.TextDocumentItem; +import org.eclipse.lsp4j.jsonrpc.util.Preconditions; +import org.eclipse.lsp4j.jsonrpc.validation.NonNull; /** * Text document extends LSP4j {@link TextDocumentItem} to provide methods to * retrieve position. * */ -public class TextDocument extends TextDocumentItem { +public class TextDocument { private static final Logger LOGGER = Logger.getLogger(TextDocument.class.getName()); + + // Consolidate CompositeCharSequence after this many updates to prevent deep nesting + private static final int CONSOLIDATION_THRESHOLD = 100; + + /** + * The text document's uri. + */ + @NonNull + private String uri; + + /** + * The text document's language identifier + */ + @NonNull + private String languageId; + + /** + * The version number of this document (it will strictly increase after each + * change, including undo/redo). + */ + private int version; + + /** + * The content of the opened text document. + */ + // CharSequence-based text storage for memory-efficient incremental updates + @NonNull + private ImmutableCharSequence text; private final Object lock = new Object(); @@ -38,16 +71,19 @@ public class TextDocument extends TextDocumentItem { private ILineTracker lineTracker; private boolean incremental; + + // Counter for tracking when to consolidate CompositeCharSequence + private int updatesSinceConsolidation = 0; public TextDocument(TextDocumentItem document) { this(document.getText(), document.getUri()); - super.setVersion(document.getVersion()); - super.setLanguageId(document.getLanguageId()); + this.setVersion(document.getVersion()); + this.setLanguageId(document.getLanguageId()); } public TextDocument(String text, String uri) { - super.setUri(uri); - super.setText(text); + this.setUri(uri); + this.text = ImmutableCharSequenceImpl.fromString(text); } public void setIncremental(boolean incremental) { @@ -74,8 +110,8 @@ public int offsetAt(Position position) throws BadLocationException { 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); + CharSequence text = getTextSequence(); + return text.subSequence(line.offset, line.offset + line.length).toString(); } public int lineOffsetAt(int position) throws BadLocationException { @@ -115,8 +151,8 @@ public Range getWordRangeAt(int textOffset, Pattern wordDefinition) { Position pos = positionAt(textOffset); ILineTracker lineTracker = getLineTracker(); Line line = lineTracker.getLineInformation(pos.getLine()); - String text = super.getText(); - String lineText = text.substring(line.offset, textOffset); + CharSequence text = getTextSequence(); + String lineText = text.subSequence(line.offset, textOffset).toString(); int position = lineText.length(); Matcher m = wordDefinition.matcher(lineText); int currentPosition = 0; @@ -149,7 +185,7 @@ private synchronized ILineTracker createLineTracker() { return lineTracker; } ILineTracker lineTracker = isIncremental() ? new TreeLineTracker(new ListLineTracker()) : new ListLineTracker(); - lineTracker.set(super.getText()); + lineTracker.set(getTextSequence()); return lineTracker; } @@ -168,31 +204,57 @@ public void update(List changes) { try { long start = System.currentTimeMillis(); synchronized (lock) { - // Initialize buffer and line tracker from the current text document - StringBuilder buffer = new StringBuilder(getText()); + // Get current text as CharSequence (no copy) + CharSequence currentText = getTextSequence(); // 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) { Integer rangeLength = changeEvent.getRangeLength(); - length = rangeLength != null ? rangeLength.intValue() : offsetAt(range.getEnd()) - offsetAt(range.getStart()); + int startOffset = offsetAt(range.getStart()); + int length; + + if (rangeLength != null) { + // Use rangeLength if provided (preferred) + length = rangeLength.intValue(); + } else { + // Calculate length from range.end + int endOffset = offsetAt(range.getEnd()); + length = endOffset - startOffset; + } + + String text = changeEvent.getText(); + + // Use CompositeCharSequence for zero-copy update + // This avoids copying the entire document text + ImmutableCharSequence newText = CompositeCharSequence.replaceRange(currentText, startOffset, + startOffset + length, ImmutableCharSequenceImpl.fromString(text)); + + lineTracker.replace(startOffset, length, text); + setText(newText); + + // IMPORTANT: Update currentText for next iteration + currentText = newText; + + // Periodically consolidate to prevent deep nesting + updatesSinceConsolidation++; + if (updatesSinceConsolidation >= CONSOLIDATION_THRESHOLD) { + consolidateText(); + currentText = getTextSequence(); + updatesSinceConsolidation = 0; + } } else { - // range is optional and if not given, the whole file content is replaced - length = buffer.length(); - range = new Range(positionAt(0), positionAt(length)); + // Full replacement + setText(changeEvent.getText()); + lineTracker.set(changeEvent.getText()); + currentText = getTextSequence(); + updatesSinceConsolidation = 0; } - 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) { @@ -209,4 +271,97 @@ public void update(List changes) { } } } + + /** + * Set the text content from a String. Converts to ImmutableCharSequence + * internally. + * + * @param text the new text content + */ + private void setText(String text) { + this.text = ImmutableCharSequenceImpl.fromString(text); + } + + private void setText(ImmutableCharSequence text) { + this.text = text; + } + + /** + * Consolidate the current text by converting CompositeCharSequence to a simple + * ImmutableCharSequenceImpl. This prevents deep nesting of CompositeCharSequence + * objects which can consume excessive memory. + */ + private void consolidateText() { + if (text instanceof CompositeCharSequence) { + // Convert to String and back to ImmutableCharSequenceImpl + // This flattens the structure + String textString = text.toString(); + this.text = ImmutableCharSequenceImpl.fromString(textString); + } + } + + public int getTextLength() { + return getTextSequence().length(); + } + + /** + * The text document's uri. + */ + @NonNull + public String getUri() { + return this.uri; + } + + /** + * The text document's uri. + */ + public void setUri(@NonNull final String uri) { + this.uri = Preconditions.checkNotNull(uri, "uri"); + } + + /** + * The text document's language identifier + */ + @NonNull + public String getLanguageId() { + return this.languageId; + } + + /** + * The text document's language identifier + */ + public void setLanguageId(@NonNull final String languageId) { + this.languageId = Preconditions.checkNotNull(languageId, "languageId"); + } + + /** + * The version number of this document (it will strictly increase after each + * change, including undo/redo). + */ + public int getVersion() { + return this.version; + } + + /** + * The version number of this document (it will strictly increase after each + * change, including undo/redo). + */ + public void setVersion(final int version) { + this.version = version; + } + + /** + * The content of the opened text document. + */ + public CharSequence getTextSequence() { + return text; + } + + /** + * The content of the opened text document. + */ + public String getText() { + return text.toString(); + } + } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/TreeLineTracker.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/TreeLineTracker.java index 86a7cccdb..58ce43637 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/TreeLineTracker.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/TreeLineTracker.java @@ -693,7 +693,7 @@ private void rebalanceAfterInsertionLeft(Node node) { } @Override - public final void replace(int offset, int length, String text) throws BadLocationException { + public final void replace(int offset, int length, CharSequence text) throws BadLocationException { if (ASSERT) { checkTree(); } @@ -750,7 +750,7 @@ public final void replace(int offset, int length, String text) throws BadLocatio * @param firstLineDelta the number of characters from the replacement offset to * the end of node > length */ - private void replaceInternal(Node node, String text, int length, int firstLineDelta) { + private void replaceInternal(Node node, CharSequence text, int length, int firstLineDelta) { // 1) modification on a single line DelimiterInfo info = text == null ? null : nextDelimiterInfo(text, 0); @@ -797,7 +797,7 @@ private void replaceInternal(Node node, String text, int length, int firstLineDe * offset to the end of node, <= * length */ - private void replaceFromTo(Node node, Node last, String text, int length, int firstLineDelta) { + private void replaceFromTo(Node node, Node last, CharSequence text, int length, int firstLineDelta) { // 2) modification covers several lines // delete intermediate nodes @@ -1179,7 +1179,7 @@ private void fail(int offset) throws BadLocationException { * @param offset the offset in the given text * @return the information of the first found delimiter or null */ - protected DelimiterInfo nextDelimiterInfo(String text, int offset) { + protected DelimiterInfo nextDelimiterInfo(CharSequence text, int offset) { char ch; int length = text.length(); for (int i = offset; i < length; i++) { @@ -1222,7 +1222,7 @@ public final String getLineDelimiter(int line) throws BadLocationException { } @Override - public final int computeNumberOfLines(String text) { + public final int computeNumberOfLines(CharSequence text) { int count = 0; int start = 0; DelimiterInfo delimiterInfo = nextDelimiterInfo(text, start); @@ -1416,7 +1416,7 @@ public final Line getLineInformation(int line) throws BadLocationException { } @Override - public final void set(String text) { + public final void set(CharSequence text) { fRoot = new Node(0, NO_DELIM); try { replace(0, 0, text); diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/text/CharSequenceBackedByChars.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/text/CharSequenceBackedByChars.java new file mode 100644 index 000000000..fdd281a68 --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/text/CharSequenceBackedByChars.java @@ -0,0 +1,113 @@ +/** + * Copyright (c) 2024 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 (inspired by IntelliJ Platform) + */ +package org.eclipse.lemminx.commons.text; + +/** + * CharSequence backed by a char array. Provides zero-copy substring operations. + * Inspired by IntelliJ Platform's CharArrayCharSequence. + */ +public class CharSequenceBackedByChars implements CharSequence { + + private final char[] chars; + private final int start; + private final int end; + + public CharSequenceBackedByChars(char[] chars) { + this(chars, 0, chars.length); + } + + public CharSequenceBackedByChars(char[] chars, int start, int end) { + if (start < 0 || end > chars.length || start > end) { + throw new IndexOutOfBoundsException("start: " + start + ", end: " + end + ", length: " + chars.length); + } + this.chars = chars; + this.start = start; + this.end = end; + } + + @Override + public int length() { + return end - start; + } + + @Override + public char charAt(int index) { + if (index < 0 || index >= length()) { + throw new IndexOutOfBoundsException("index: " + index + ", length: " + length()); + } + return chars[start + index]; + } + + @Override + public CharSequence subSequence(int start, int end) { + if (start < 0 || end > length() || start > end) { + throw new IndexOutOfBoundsException("start: " + start + ", end: " + end + ", length: " + length()); + } + return new CharSequenceBackedByChars(chars, this.start + start, this.start + end); + } + + @Override + public String toString() { + return new String(chars, start, end - start); + } + + /** + * Get the underlying char array (for efficient operations). + * WARNING: Do not modify the returned array! + */ + public char[] getChars() { + return chars; + } + + public int getStart() { + return start; + } + + public int getEnd() { + return end; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + + // Compare with CharSequence (including String) character by character + // to avoid creating String copies via toString() + if (obj instanceof CharSequence) { + CharSequence other = (CharSequence) obj; + int len = length(); + if (len != other.length()) return false; + + for (int i = 0; i < len; i++) { + if (charAt(i) != other.charAt(i)) return false; + } + return true; + } + + return false; + } + + @Override + public int hashCode() { + // Use same hashCode as String for consistency + int h = 0; + int len = length(); + for (int i = 0; i < len; i++) { + h = 31 * h + charAt(i); + } + return h; + } +} + +// Made with Bob diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/text/CharSequenceReader.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/text/CharSequenceReader.java new file mode 100644 index 000000000..01e976ce5 --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/text/CharSequenceReader.java @@ -0,0 +1,32 @@ +package org.eclipse.lemminx.commons.text; + +import java.io.IOException; +import java.io.Reader; + +final class CharSequenceReader extends Reader { + + private final CharSequence charSequence; + private int position = 0; + + public CharSequenceReader(CharSequence charSequence) { + this.charSequence = charSequence; + } + + @Override + public int read(char[] cbuf, int off, int len) { + if (position >= charSequence.length()) { + return -1; + } + + int charsToRead = Math.min(len, charSequence.length() - position); + for (int i = 0; i < charsToRead; i++) { + cbuf[off + i] = charSequence.charAt(position++); + } + return charsToRead; + } + + @Override + public void close() throws IOException { + // rien à fermer + } +} \ No newline at end of file diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/text/CharSequenceUtils.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/text/CharSequenceUtils.java new file mode 100644 index 000000000..752c8e31e --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/text/CharSequenceUtils.java @@ -0,0 +1,49 @@ +package org.eclipse.lemminx.commons.text; + +import java.io.Reader; + +public class CharSequenceUtils { + + public static Reader newReader(CharSequence charSequence) { + return new CharSequenceReader(charSequence); + } + + public static int indexOf(CharSequence text, CharSequence pattern) { + int textLength = text.length(); + int patternLength = pattern.length(); + + if (patternLength == 0) return 0; + if (patternLength > textLength) return -1; + + outer: + for (int i = 0; i <= textLength - patternLength; i++) { + for (int j = 0; j < patternLength; j++) { + if (text.charAt(i + j) != pattern.charAt(j)) { + continue outer; + } + } + return i; + } + return -1; + } + + public static int indexOf(CharSequence text, CharSequence pattern, int fromIndex) { + int textLength = text.length(); + int patternLength = pattern.length(); + + if (fromIndex < 0) fromIndex = 0; + if (patternLength == 0) return fromIndex; + if (patternLength > textLength) return -1; + + outer: + for (int i = fromIndex; i <= textLength - patternLength; i++) { + for (int j = 0; j < patternLength; j++) { + if (text.charAt(i + j) != pattern.charAt(j)) { + continue outer; + } + } + return i; + } + return -1; + } +} diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/text/CompositeCharSequence.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/text/CompositeCharSequence.java new file mode 100644 index 000000000..062a15337 --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/text/CompositeCharSequence.java @@ -0,0 +1,258 @@ +/** + * Copyright (c) 2024 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 (inspired by IntelliJ Platform) + */ +package org.eclipse.lemminx.commons.text; + +import java.util.Arrays; + +/** + * Composite CharSequence that concatenates multiple CharSequences without copying. + * This allows efficient incremental text updates by reusing unchanged segments. + * + * Example: For "Hello World" → "Hello Beautiful World" + * Instead of copying the entire text, we create: + * CompositeCharSequence("Hello ", "Beautiful ", "World") + * + * Inspired by IntelliJ Platform's StringUtil.join() and rope data structures. + */ +public class CompositeCharSequence implements ImmutableCharSequence { + + private final CharSequence[] segments; + private final int[] offsets; // Cumulative offsets for each segment + private final int totalLength; + private String stringCache; + + public CompositeCharSequence(CharSequence... segments) { + if (segments == null || segments.length == 0) { + throw new IllegalArgumentException("Segments cannot be null or empty"); + } + + this.segments = segments; + this.offsets = new int[segments.length]; + + int length = 0; + for (int i = 0; i < segments.length; i++) { + offsets[i] = length; + length += segments[i].length(); + } + this.totalLength = length; + } + + @Override + public int length() { + return totalLength; + } + + @Override + public char charAt(int index) { + if (index < 0 || index >= totalLength) { + throw new IndexOutOfBoundsException("index: " + index + ", length: " + totalLength); + } + + // Binary search to find the segment + int segmentIndex = findSegmentIndex(index); + int localIndex = index - offsets[segmentIndex]; + return segments[segmentIndex].charAt(localIndex); + } + + @Override + public ImmutableCharSequence subSequence(int start, int end) { + if (start < 0 || end > totalLength || start > end) { + throw new IndexOutOfBoundsException("start: " + start + ", end: " + end + ", length: " + totalLength); + } + + if (start == 0 && end == totalLength) { + return this; + } + + // Zero-copy subSequence implementation + // Find which segments are involved and create a new CompositeCharSequence + int startSegmentIndex = findSegmentIndex(start); + int endSegmentIndex = findSegmentIndex(end - 1); // end is exclusive + + if (startSegmentIndex == endSegmentIndex) { + // SubSequence is within a single segment - delegate to that segment + int localStart = start - offsets[startSegmentIndex]; + int localEnd = end - offsets[startSegmentIndex]; + CharSequence segment = segments[startSegmentIndex]; + + if (segment instanceof ImmutableCharSequence) { + return ((ImmutableCharSequence) segment).subSequence(localStart, localEnd); + } + // Fallback for non-immutable segments + return ImmutableCharSequenceImpl.fromString(segment.subSequence(localStart, localEnd).toString()); + } + + // SubSequence spans multiple segments - create a new CompositeCharSequence + int newSegmentCount = endSegmentIndex - startSegmentIndex + 1; + CharSequence[] newSegments = new CharSequence[newSegmentCount]; + + for (int i = 0; i < newSegmentCount; i++) { + int segmentIndex = startSegmentIndex + i; + CharSequence segment = segments[segmentIndex]; + + if (i == 0) { + // First segment: may need to trim from start + int localStart = start - offsets[segmentIndex]; + if (localStart > 0) { + newSegments[i] = segment.subSequence(localStart, segment.length()); + } else { + newSegments[i] = segment; + } + } else if (i == newSegmentCount - 1) { + // Last segment: may need to trim from end + int localEnd = end - offsets[segmentIndex]; + if (localEnd < segment.length()) { + newSegments[i] = segment.subSequence(0, localEnd); + } else { + newSegments[i] = segment; + } + } else { + // Middle segments: use as-is + newSegments[i] = segment; + } + } + + return new CompositeCharSequence(newSegments); + } + + @Override + public String toString() { + if (stringCache == null) { + StringBuilder sb = new StringBuilder(totalLength); + for (CharSequence segment : segments) { + sb.append(segment); + } + stringCache = sb.toString(); + } + return stringCache; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + + // Compare with CharSequence (including String) character by character + // to avoid creating String copies via toString() + if (obj instanceof CharSequence) { + CharSequence other = (CharSequence) obj; + if (length() != other.length()) return false; + + for (int i = 0; i < length(); i++) { + if (charAt(i) != other.charAt(i)) return false; + } + return true; + } + + return false; + } + + @Override + public int hashCode() { + // Use cached String's hashCode if available, otherwise compute it + if (stringCache != null) { + return stringCache.hashCode(); + } + + // Compute hashCode same way as String does + int h = 0; + int len = length(); + for (int i = 0; i < len; i++) { + h = 31 * h + charAt(i); + } + return h; + } + + /** + * Find the segment index for a given character position using binary search. + */ + private int findSegmentIndex(int position) { + int low = 0; + int high = segments.length - 1; + + while (low < high) { + int mid = (low + high + 1) / 2; + if (offsets[mid] <= position) { + low = mid; + } else { + high = mid - 1; + } + } + + return low; + } + + /** + * Create a composite CharSequence by replacing a range in the original sequence. + * This is the key method for incremental updates without copying. + * + * @param original The original CharSequence + * @param start Start offset of the range to replace + * @param end End offset of the range to replace + * @param replacement The replacement text + * @return A new CompositeCharSequence with the replacement applied + */ + public static ImmutableCharSequence replaceRange(CharSequence original, int start, int end, CharSequence replacement) { + if (start < 0 || end > original.length() || start > end) { + throw new IndexOutOfBoundsException("start: " + start + ", end: " + end + ", length: " + original.length()); + } + + // If replacing the entire text, just return the replacement + if (start == 0 && end == original.length()) { + if (replacement instanceof ImmutableCharSequence) { + return (ImmutableCharSequence) replacement; + } + return ImmutableCharSequenceImpl.fromString(replacement.toString()); + } + + // CRITICAL: Convert original to ImmutableCharSequence if it's a String + // This ensures subSequence() operations are zero-copy + ImmutableCharSequence immutableOriginal; + if (original instanceof ImmutableCharSequence) { + immutableOriginal = (ImmutableCharSequence) original; + } else { + // Convert String to ImmutableCharSequence (one-time copy) + immutableOriginal = ImmutableCharSequenceImpl.fromString(original.toString()); + } + + // Build segments: before + replacement + after (now zero-copy) + CharSequence before = start > 0 ? immutableOriginal.subSequence(0, start) : null; + CharSequence after = end < immutableOriginal.length() ? immutableOriginal.subSequence(end, immutableOriginal.length()) : null; + + // Count non-null segments + int segmentCount = (before != null ? 1 : 0) + (replacement.length() > 0 ? 1 : 0) + (after != null ? 1 : 0); + + if (segmentCount == 0) { + return ImmutableCharSequenceImpl.fromString(""); + } + + if (segmentCount == 1) { + CharSequence single = before != null ? before : (replacement.length() > 0 ? replacement : after); + if (single instanceof ImmutableCharSequence) { + return (ImmutableCharSequence) single; + } + return ImmutableCharSequenceImpl.fromString(single.toString()); + } + + // Build composite + CharSequence[] segments = new CharSequence[segmentCount]; + int index = 0; + if (before != null) segments[index++] = before; + if (replacement.length() > 0) segments[index++] = replacement; + if (after != null) segments[index++] = after; + + return new CompositeCharSequence(segments); + } +} + +// Made with Bob diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/text/ImmutableCharSequence.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/text/ImmutableCharSequence.java new file mode 100644 index 000000000..f17ff924d --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/text/ImmutableCharSequence.java @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2024 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 (inspired by IntelliJ Platform) + */ +package org.eclipse.lemminx.commons.text; + +/** + * Marker interface for immutable CharSequences. + * Guarantees that the content will never change, allowing safe sharing and zero-copy operations. + * + * Inspired by IntelliJ Platform's immutability guarantees. + */ +public interface ImmutableCharSequence extends CharSequence { + + /** + * Returns an immutable subsequence. The returned sequence is also immutable. + */ + @Override + ImmutableCharSequence subSequence(int start, int end); +} + +// Made with Bob diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/text/ImmutableCharSequenceImpl.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/text/ImmutableCharSequenceImpl.java new file mode 100644 index 000000000..e0a393851 --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/text/ImmutableCharSequenceImpl.java @@ -0,0 +1,105 @@ +/** + * Copyright (c) 2024 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 (inspired by IntelliJ Platform) + */ +package org.eclipse.lemminx.commons.text; + +/** + * Immutable CharSequence implementation backed by a char array. + * Provides zero-copy substring operations. + * + * Inspired by IntelliJ Platform's CharArrayCharSequence. + */ +public class ImmutableCharSequenceImpl extends CharSequenceBackedByChars implements ImmutableCharSequence { + + private String stringCache; + + public ImmutableCharSequenceImpl(char[] chars) { + super(chars); + } + + public ImmutableCharSequenceImpl(char[] chars, int start, int end) { + super(chars, start, end); + } + + public ImmutableCharSequenceImpl(String text) { + this(text.toCharArray()); + this.stringCache = text; + } + + @Override + public ImmutableCharSequence subSequence(int start, int end) { + if (start < 0 || end > length() || start > end) { + throw new IndexOutOfBoundsException("start: " + start + ", end: " + end + ", length: " + length()); + } + if (start == 0 && end == length()) { + return this; + } + return new ImmutableCharSequenceImpl(getChars(), getStart() + start, getStart() + end); + } + + @Override + public String toString() { + if (stringCache == null) { + stringCache = super.toString(); + } + return stringCache; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + + // Compare with CharSequence (including String) character by character + if (obj instanceof CharSequence) { + CharSequence other = (CharSequence) obj; + int len = length(); + if (len != other.length()) return false; + + for (int i = 0; i < len; i++) { + if (charAt(i) != other.charAt(i)) return false; + } + return true; + } + + return false; + } + + @Override + public int hashCode() { + // Use cached String's hashCode if available, otherwise compute it + if (stringCache != null) { + return stringCache.hashCode(); + } + + // Compute hashCode same way as String does + int h = 0; + int len = length(); + for (int i = 0; i < len; i++) { + h = 31 * h + charAt(i); + } + return h; + } + + /** + * Create an immutable CharSequence from a String. + * The String is converted to char[] to allow zero-copy subSequence operations. + */ + public static ImmutableCharSequence fromString(String text) { + if (text == null) { + return null; + } + return new ImmutableCharSequenceImpl(text); + } +} + +// Made with Bob diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMAttr.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMAttr.java index 19c5419e4..1ba927f61 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMAttr.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMAttr.java @@ -90,7 +90,7 @@ public String getName() { // Memory optimization: Extract name from document instead of caching if (nameStart != NULL_VALUE && nameEnd != NULL_VALUE) { // Name is in the document, extract it - return getOwnerDocument().getText().substring(nameStart, nameEnd); + return getOwnerDocument().getTextSequence().subSequence(nameStart, nameEnd).toString(); } // Name was set programmatically or doesn't exist return name; @@ -228,7 +228,7 @@ public boolean hasDelimiter() { public String getOriginalValue() { // Memory optimization: Extract from document instead of caching if (valueStart != NULL_VALUE && delimiter < valueStart) { - return getOwnerDocument().getText().substring(valueStart, valueEnd); + return getOwnerDocument().getTextSequence().subSequence(valueStart, valueEnd).toString(); } return value; } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMCharacterData.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMCharacterData.java index b3cc42d60..08f477c0e 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMCharacterData.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMCharacterData.java @@ -137,7 +137,7 @@ public int getEndContent() { public String getData() { // No caching - extract directly from document to save memory // The document text is already in memory, so this is just a substring operation - return getOwnerDocument().getText().substring(getStartContent(), getEndContent()); + return getOwnerDocument().getTextSequence().subSequence(getStartContent(), getEndContent()).toString(); } /* diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMDocument.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMDocument.java index 28d785af3..06fed6e60 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMDocument.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMDocument.java @@ -67,7 +67,7 @@ public class DOMDocument extends DOMNode implements Document { private String externalGrammarFromNamespaceURI; public DOMDocument(TextDocument textDocument, URIResolverExtensionManager resolverExtensionManager) { - super(0, textDocument.getText().length()); + super(0, textDocument.getTextSequence().length()); this.textDocument = textDocument; this.resolverExtensionManager = resolverExtensionManager; resetGrammar(); @@ -136,10 +136,19 @@ public String getNamespaceURI() { * * @return the text content of the XML document. */ - public String getText() { - return textDocument.getText(); + public CharSequence getTextSequence() { + return textDocument.getTextSequence(); } + /** + * Returns the text content of the XML document. + * + * @return the text content of the XML document. + */ + public CharSequence getText() { + return textDocument.getText(); + } + public TextDocument getTextDocument() { return textDocument; } @@ -892,7 +901,7 @@ public Range getTrimmedRange(Range range) { } public Range getTrimmedRange(int start, int end) { - String text = getText(); + CharSequence text = getTextSequence(); char c = text.charAt(start); while (Character.isWhitespace(c)) { start++; diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMDocumentType.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMDocumentType.java index 25e0bf33c..0a53a3fa8 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMDocumentType.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMDocumentType.java @@ -47,7 +47,7 @@ public DOMDocumentType getOwnerDocType() { @Override public String getTextContent() { if (content == null) { - content = getOwnerDocument().getText().substring(getStart(), getEnd()); + content = getOwnerDocument().getTextSequence().subSequence(getStart(), getEnd()).toString(); } return content; } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMElement.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMElement.java index f0351c635..8011e7d63 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMElement.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMElement.java @@ -32,7 +32,7 @@ */ public class DOMElement extends DOMNode implements org.w3c.dom.Element { - String tag; + CharSequence tag; boolean selfClosed; // DomElement.start == startTagOpenOffset @@ -74,6 +74,10 @@ public String getNodeName() { */ @Override public String getTagName() { + return tag != null ? tag.toString() : null; + } + + public CharSequence getTag() { return tag; } @@ -223,8 +227,8 @@ public String getPrefix(String namespaceURI) { } if (!StringUtils.isEmpty(namespaceURI)) { switch (namespaceURI) { - case "http://www.w3.org/XML/1998/namespace": - return "xml"; + case "http://www.w3.org/XML/1998/namespace": + return "xml"; } } return null; @@ -249,7 +253,7 @@ public boolean isSelfClosed() { * position after the character you want to start at. */ public Integer endsWith(char c, int startOffset) { - String text = this.getOwnerDocument().getText(); + CharSequence text = this.getOwnerDocument().getTextSequence(); if (startOffset > text.length() || startOffset < 0) { return null; } @@ -276,7 +280,7 @@ public Integer endsWith(char c, int startOffset) { * @return true if the given tag is the same tag of this element and false * otherwise. */ - public boolean isSameTag(String tag) { + public boolean isSameTag(CharSequence tag) { return Objects.equals(this.tag, tag); } @@ -405,7 +409,7 @@ public boolean isEndTagClosed() { */ public int getOffsetAfterStartTag() { if (hasTagName()) { - return getStartTagOpenOffset() + getTagName().length() + 1; + return getStartTagOpenOffset() + getTag().length() + 1; } return getStartTagOpenOffset() + 1; } @@ -458,7 +462,7 @@ public boolean isOrphanEndTagOf(String tagName) { * with an angle bracket */ public int getUnclosedStartTagCloseOffset() { - String documentText = getOwnerDocument().getText(); + CharSequence documentText = getOwnerDocument().getTextSequence(); int i = getStart() + 1; for (; i < documentText.length() && documentText.charAt(i) != '/' && documentText.charAt(i) != '<'; i++) { } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMParser.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMParser.java index a88dfb52c..f59de044e 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMParser.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMParser.java @@ -47,13 +47,13 @@ private DOMParser() { } - public DOMDocument parse(String text, String uri, URIResolverExtensionManager resolverExtensionManager) { - return parse(new TextDocument(text, uri), resolverExtensionManager); + public DOMDocument parse(CharSequence text, String uri, URIResolverExtensionManager resolverExtensionManager) { + return parse(new TextDocument(text.toString(), uri), resolverExtensionManager); } - public DOMDocument parse(String text, String uri, URIResolverExtensionManager resolverExtensionManager, + public DOMDocument parse(CharSequence text, String uri, URIResolverExtensionManager resolverExtensionManager, boolean ignoreWhitespaceContent) { - return parse(new TextDocument(text, uri), resolverExtensionManager, ignoreWhitespaceContent); + return parse(new TextDocument(text.toString(), uri), resolverExtensionManager, ignoreWhitespaceContent); } public DOMDocument parse(TextDocument document, URIResolverExtensionManager resolverExtensionManager) { @@ -69,7 +69,7 @@ public DOMDocument parse(TextDocument document, URIResolverExtensionManager reso boolean ignoreWhitespaceContent, CancelChecker monitor) { boolean isDTD = DOMUtils.isDTD(document.getUri()); boolean inDTDInternalSubset = false; - String text = document.getText(); + CharSequence text = document.getTextSequence(); Scanner scanner = XMLScanner.createScanner(text, 0, isDTD); DOMDocument xmlDocument = new DOMDocument(document, resolverExtensionManager); xmlDocument.setCancelChecker(monitor); @@ -129,552 +129,540 @@ public DOMDocument parse(TextDocument document, URIResolverExtensionManager reso } } 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 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 StartTag: { + case StartTagClose: + if (curr.isElement()) { DOMElement element = (DOMElement) curr; - element.tag = scanner.getTokenText(); - curr.end = scanner.getTokenEnd(); - break; + curr.end = scanner.getTokenEnd(); // might be later set to end tag position + element.startTagCloseOffset = scanner.getTokenOffset(); + } 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; + } + 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: ) + CharSequence closeTag = scanner.getTokenText(); + DOMNode current = curr; + + /** + * eg: will set a,b,c end position to the start of | + */ + while (!(curr.isElement() && ((DOMElement) curr).isSameTag(closeTag)) && curr.parent != null) { + curr.end = endTagOpenOffset; + curr = curr.parent; } - - case StartTagClose: + if (curr != xmlDocument) { + curr.setClosed(true); 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.setClosed(true); - curr = curr.parent; - } + ((DOMElement) curr).endTagOpenOffset = endTagOpenOffset; } 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.setClosed(true); - curr = curr.parent; - } + ((DOMProcessingInstruction) curr).endTagOpenOffset = endTagOpenOffset; } 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; + } else { + // element open tag not found (ex: ) 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 EndTag: - // end tag (ex: ) - String closeTag = scanner.getTokenText(); - DOMNode current = curr; + case StartTagSelfClose: + if (curr.parent != null) { + curr.setClosed(true); + ((DOMElement) curr).selfClosed = true; + curr.end = scanner.getTokenEnd(); + lastClosed = curr; + curr = curr.parent; + } + break; - /** - * eg: will set a,b,c end position to the start of | - */ - while (!(curr.isElement() && ((DOMElement) curr).isSameTag(closeTag)) && curr.parent != null) { - curr.end = endTagOpenOffset; - curr = curr.parent; - } - if (curr != xmlDocument) { - curr.setClosed(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: ) 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; + case EndTagClose: + if (curr.parent != null) { + curr.end = scanner.getTokenEnd(); + lastClosed = curr; + if (lastClosed.isElement()) { + ((DOMElement) curr).endTagCloseOffset = scanner.getTokenOffset(); } - break; - - case StartTagSelfClose: - if (curr.parent != null) { + if (curr.isDoctype()) { curr.setClosed(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.setClosed(true); - } - curr = curr.parent; - } - break; + curr = curr.parent; - case AttributeName: { - attr = new DOMAttr(null, scanner.getTokenOffset(), - scanner.getTokenEnd(), curr); - curr.setAttributeNode(attr); - curr.end = scanner.getTokenEnd(); - break; } + break; - case DelimiterAssign: { - if (attr != null) { - // Sets the value to the '=' position in case there is no AttributeValue - attr.setDelimiter(scanner.getTokenOffset()); - } - break; - } + case AttributeName: { + attr = new DOMAttr(null, scanner.getTokenOffset(), scanner.getTokenEnd(), curr); + curr.setAttributeNode(attr); + curr.end = scanner.getTokenEnd(); + break; + } - case AttributeValue: { - if (curr.hasAttributes() && attr != null) { - attr.setValue(null, scanner.getTokenOffset(), scanner.getTokenEnd()); - } - attr = null; - curr.end = scanner.getTokenEnd(); - break; + case DelimiterAssign: { + if (attr != null) { + // Sets the value to the '=' position in case there is no AttributeValue + attr.setDelimiter(scanner.getTokenOffset()); } + break; + } - case CDATATagOpen: { - DOMCDATASection cdataNode = xmlDocument.createCDataSection(scanner.getTokenOffset(), text.length()); - curr.addChild(cdataNode); - curr = cdataNode; - break; + case AttributeValue: { + if (curr.hasAttributes() && attr != null) { + attr.setValue(null, scanner.getTokenOffset(), scanner.getTokenEnd()); } + attr = null; + curr.end = scanner.getTokenEnd(); + break; + } - case CDATAContent: { - DOMCDATASection cdataNode = (DOMCDATASection) curr; - cdataNode.startContent = scanner.getTokenOffset(); - cdataNode.endContent = scanner.getTokenEnd(); - curr.end = scanner.getTokenEnd(); - break; - } + case CDATATagOpen: { + DOMCDATASection cdataNode = xmlDocument.createCDataSection(scanner.getTokenOffset(), text.length()); + curr.addChild(cdataNode); + curr = cdataNode; + break; + } - case CDATATagClose: { - curr.end = scanner.getTokenEnd(); - curr.setClosed(true); - curr = curr.parent; - break; - } + case CDATAContent: { + DOMCDATASection cdataNode = (DOMCDATASection) curr; + cdataNode.startContent = scanner.getTokenOffset(); + cdataNode.endContent = scanner.getTokenEnd(); + curr.end = scanner.getTokenEnd(); + break; + } - case StartPrologOrPI: { - DOMProcessingInstruction prologOrPINode = xmlDocument - .createProcessingInstruction(scanner.getTokenOffset(), text.length()); - curr.addChild(prologOrPINode); - curr = prologOrPINode; - break; - } + case CDATATagClose: { + curr.end = scanner.getTokenEnd(); + curr.setClosed(true); + curr = curr.parent; + break; + } - case PIName: { - DOMProcessingInstruction processingInstruction = ((DOMProcessingInstruction) curr); - processingInstruction.target = scanner.getTokenText(); - processingInstruction.processingInstruction = true; - break; - } + case StartPrologOrPI: { + DOMProcessingInstruction prologOrPINode = xmlDocument + .createProcessingInstruction(scanner.getTokenOffset(), text.length()); + curr.addChild(prologOrPINode); + curr = prologOrPINode; + break; + } - case PrologName: { - DOMProcessingInstruction processingInstruction = ((DOMProcessingInstruction) curr); - processingInstruction.target = scanner.getTokenText(); - processingInstruction.prolog = true; - break; - } + case PIName: { + DOMProcessingInstruction processingInstruction = ((DOMProcessingInstruction) curr); + processingInstruction.target = scanner.getTokenText(); + processingInstruction.processingInstruction = true; + break; + } - case PIContent: { - DOMProcessingInstruction processingInstruction = (DOMProcessingInstruction) curr; - processingInstruction.startContent = scanner.getTokenOffset(); - processingInstruction.endContent = scanner.getTokenEnd(); - break; - } + case PrologName: { + DOMProcessingInstruction processingInstruction = ((DOMProcessingInstruction) curr); + processingInstruction.target = scanner.getTokenText(); + processingInstruction.prolog = true; + break; + } - case PIEnd: - case PrologEnd: { - curr.end = scanner.getTokenEnd(); - curr.setClosed(true); - curr = curr.parent; - break; - } + case PIContent: { + DOMProcessingInstruction processingInstruction = (DOMProcessingInstruction) curr; + processingInstruction.startContent = scanner.getTokenOffset(); + processingInstruction.endContent = scanner.getTokenEnd(); + 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())) { + case PIEnd: + case PrologEnd: { + curr.end = scanner.getTokenEnd(); + curr.setClosed(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; } - 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); + } 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; } - break; + } 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 Comment: { + DOMComment comment = (DOMComment) curr; + comment.startContent = scanner.getTokenOffset(); + comment.endContent = scanner.getTokenEnd(); + break; + } - case EndCommentTag: { - curr.end = scanner.getTokenEnd(); - curr.setClosed(true); - curr = curr.parent; - break; - } + case EndCommentTag: { + curr.end = scanner.getTokenEnd(); + curr.setClosed(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(); - } + 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.setClosed(true); - - if (scanner.isTokenTextBlank()) { - if (ignoreWhitespaceContent) { - if (curr.hasChildNodes()) { - break; - } - - tempWhitespaceContent = textNode; - break; + } + int start = scanner.getTokenOffset(); + int end = scanner.getTokenEnd(); + DOMText textNode = xmlDocument.createText(start, end); + textNode.setClosed(true); - } else if (!currIsDeclNode) { - textNode.setWhitespace(true); - } else { + 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; - } + curr.addChild(textNode); + break; + } - case DTDDoctypeName: { - DOMDocumentType doctype = (DOMDocumentType) curr; - doctype.setName(scanner.getTokenOffset(), scanner.getTokenEnd()); - break; - } + // DTD - case DTDDocTypeKindPUBLIC: { - DOMDocumentType doctype = (DOMDocumentType) curr; - doctype.setKind(scanner.getTokenOffset(), scanner.getTokenEnd()); - break; - } + case DTDStartDoctypeTag: { + DOMDocumentType doctype = xmlDocument.createDocumentType(scanner.getTokenOffset(), text.length()); + curr.addChild(doctype); + doctype.parent = curr; + curr = doctype; + break; + } - case DTDDocTypeKindSYSTEM: { - DOMDocumentType doctype = (DOMDocumentType) curr; - doctype.setKind(scanner.getTokenOffset(), scanner.getTokenEnd()); - break; - } + case DTDDoctypeName: { + DOMDocumentType doctype = (DOMDocumentType) curr; + doctype.setName(scanner.getTokenOffset(), scanner.getTokenEnd()); + break; + } - case DTDDoctypePublicId: { - DOMDocumentType doctype = (DOMDocumentType) curr; - doctype.setPublicId(scanner.getTokenOffset(), scanner.getTokenEnd()); - break; - } + case DTDDocTypeKindPUBLIC: { + DOMDocumentType doctype = (DOMDocumentType) curr; + doctype.setKind(scanner.getTokenOffset(), scanner.getTokenEnd()); + break; + } - case DTDDoctypeSystemId: { - DOMDocumentType doctype = (DOMDocumentType) curr; - doctype.setSystemId(scanner.getTokenOffset(), scanner.getTokenEnd()); - break; - } + case DTDDocTypeKindSYSTEM: { + DOMDocumentType doctype = (DOMDocumentType) curr; + doctype.setKind(scanner.getTokenOffset(), scanner.getTokenEnd()); + break; + } - case DTDStartInternalSubset: { - DOMDocumentType doctype = (DOMDocumentType) curr; - doctype.setStartInternalSubset(scanner.getTokenOffset()); - inDTDInternalSubset = true; - break; - } + case DTDDoctypePublicId: { + DOMDocumentType doctype = (DOMDocumentType) curr; + doctype.setPublicId(scanner.getTokenOffset(), scanner.getTokenEnd()); + 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 DTDDoctypeSystemId: { + DOMDocumentType doctype = (DOMDocumentType) curr; + doctype.setSystemId(scanner.getTokenOffset(), scanner.getTokenEnd()); + break; + } - case DTDStartElement: { - // If previous 'curr' was an unclosed DTD Declaration - while (!curr.isDoctype()) { - curr.end = scanner.getTokenOffset(); - curr = curr.getParentNode(); - } + case DTDStartInternalSubset: { + DOMDocumentType doctype = (DOMDocumentType) curr; + doctype.setStartInternalSubset(scanner.getTokenOffset()); + inDTDInternalSubset = true; + break; + } - DTDElementDecl child = new DTDElementDecl(scanner.getTokenOffset(), text.length()); - curr.addChild(child); - curr = child; - 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 DTDElementDeclName: { - DTDElementDecl element = (DTDElementDecl) curr; - element.setName(scanner.getTokenOffset(), scanner.getTokenEnd()); - break; + case DTDStartElement: { + // If previous 'curr' was an unclosed DTD Declaration + while (!curr.isDoctype()) { + curr.end = scanner.getTokenOffset(); + curr = curr.getParentNode(); } - case DTDElementCategory: { - DTDElementDecl element = (DTDElementDecl) curr; - element.setCategory(scanner.getTokenOffset(), scanner.getTokenEnd()); - break; - } + DTDElementDecl child = new DTDElementDecl(scanner.getTokenOffset(), text.length()); + curr.addChild(child); + curr = child; + break; + } - case DTDStartElementContent: { - DTDElementDecl element = (DTDElementDecl) curr; - element.setContent(scanner.getTokenOffset(), scanner.getTokenEnd()); - break; - } + case DTDElementDeclName: { + DTDElementDecl element = (DTDElementDecl) curr; + element.setName(scanner.getTokenOffset(), scanner.getTokenEnd()); + break; + } - case DTDElementContent: { - DTDElementDecl element = (DTDElementDecl) curr; - element.updateLastParameterEnd(scanner.getTokenEnd()); - break; - } + case DTDElementCategory: { + DTDElementDecl element = (DTDElementDecl) curr; + element.setCategory(scanner.getTokenOffset(), scanner.getTokenEnd()); + break; + } - case DTDEndElementContent: { - DTDElementDecl element = (DTDElementDecl) curr; - element.updateLastParameterEnd(scanner.getTokenEnd()); - break; - } + case DTDStartElementContent: { + DTDElementDecl element = (DTDElementDecl) curr; + element.setContent(scanner.getTokenOffset(), 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()); + case DTDElementContent: { + DTDElementDecl element = (DTDElementDecl) curr; + element.updateLastParameterEnd(scanner.getTokenEnd()); + break; + } - isInitialDeclaration = true; - curr.addChild(child); - curr = child; - break; - } + case DTDEndElementContent: { + DTDElementDecl element = (DTDElementDecl) curr; + element.updateLastParameterEnd(scanner.getTokenEnd()); + break; + } - case DTDAttlistElementName: { - DTDAttlistDecl attribute = (DTDAttlistDecl) curr; - attribute.setName(scanner.getTokenOffset(), 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 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; + case DTDAttlistElementName: { + DTDAttlistDecl attribute = (DTDAttlistDecl) curr; + attribute.setName(scanner.getTokenOffset(), scanner.getTokenEnd()); + break; + } - attribute = child; - curr = child; - } - attribute.setAttributeName(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; - case DTDAttlistAttributeType: { - DTDAttlistDecl attribute = (DTDAttlistDecl) curr; - attribute.setAttributeType(scanner.getTokenOffset(), scanner.getTokenEnd()); - break; + attribute = child; + curr = child; } + attribute.setAttributeName(scanner.getTokenOffset(), scanner.getTokenEnd()); + break; + } - case DTDAttlistAttributeValue: { - DTDAttlistDecl attribute = (DTDAttlistDecl) curr; - attribute.setAttributeValue(scanner.getTokenOffset(), scanner.getTokenEnd()); + case DTDAttlistAttributeType: { + DTDAttlistDecl attribute = (DTDAttlistDecl) curr; + attribute.setAttributeType(scanner.getTokenOffset(), scanner.getTokenEnd()); + break; + } - if (attribute.parent.isDTDAttListDecl()) { // Is not the root/main ATTLIST node - curr = attribute.parent; - } else { - isInitialDeclaration = false; - } - break; - } + case DTDAttlistAttributeValue: { + DTDAttlistDecl attribute = (DTDAttlistDecl) curr; + attribute.setAttributeValue(scanner.getTokenOffset(), scanner.getTokenEnd()); - 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; + if (attribute.parent.isDTDAttListDecl()) { // Is not the root/main ATTLIST node + curr = attribute.parent; + } else { + isInitialDeclaration = false; } + break; + } - case DTDEntityPercent: { - DTDEntityDecl entity = (DTDEntityDecl) curr; - entity.setPercent(scanner.getTokenOffset(), scanner.getTokenEnd()); - 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 DTDEntityName: { - DTDEntityDecl entity = (DTDEntityDecl) curr; - entity.setName(scanner.getTokenOffset(), scanner.getTokenEnd()); - break; - } + case DTDEntityPercent: { + DTDEntityDecl entity = (DTDEntityDecl) curr; + entity.setPercent(scanner.getTokenOffset(), scanner.getTokenEnd()); + break; + } - case DTDEntityValue: { - DTDEntityDecl entity = (DTDEntityDecl) curr; - entity.setValue(scanner.getTokenOffset(), scanner.getTokenEnd()); - break; - } + case DTDEntityName: { + DTDEntityDecl entity = (DTDEntityDecl) curr; + entity.setName(scanner.getTokenOffset(), scanner.getTokenEnd()); + break; + } - case DTDEntityKindPUBLIC: - case DTDEntityKindSYSTEM: { - DTDEntityDecl entity = (DTDEntityDecl) curr; - entity.setKind(scanner.getTokenOffset(), scanner.getTokenEnd()); - break; - } + case DTDEntityValue: { + DTDEntityDecl entity = (DTDEntityDecl) curr; + entity.setValue(scanner.getTokenOffset(), scanner.getTokenEnd()); + break; + } - case DTDEntityPublicId: { - DTDEntityDecl entity = (DTDEntityDecl) curr; - entity.setPublicId(scanner.getTokenOffset(), scanner.getTokenEnd()); - break; - } + case DTDEntityKindPUBLIC: + case DTDEntityKindSYSTEM: { + DTDEntityDecl entity = (DTDEntityDecl) curr; + entity.setKind(scanner.getTokenOffset(), scanner.getTokenEnd()); + break; + } - case DTDEntitySystemId: { - DTDEntityDecl entity = (DTDEntityDecl) curr; - entity.setSystemId(scanner.getTokenOffset(), scanner.getTokenEnd()); - break; - } + case DTDEntityPublicId: { + DTDEntityDecl entity = (DTDEntityDecl) curr; + entity.setPublicId(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 DTDEntitySystemId: { + DTDEntityDecl entity = (DTDEntityDecl) curr; + entity.setSystemId(scanner.getTokenOffset(), scanner.getTokenEnd()); + break; + } - case DTDNotationName: { - DTDNotationDecl notation = (DTDNotationDecl) curr; - notation.setName(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 DTDNotationKindPUBLIC: { - DTDNotationDecl notation = (DTDNotationDecl) curr; - notation.setKind(scanner.getTokenOffset(), scanner.getTokenEnd()); - break; - } + case DTDNotationName: { + DTDNotationDecl notation = (DTDNotationDecl) curr; + notation.setName(scanner.getTokenOffset(), scanner.getTokenEnd()); + break; + } - case DTDNotationKindSYSTEM: { - DTDNotationDecl notation = (DTDNotationDecl) curr; - notation.setKind(scanner.getTokenOffset(), scanner.getTokenEnd()); - break; - } + case DTDNotationKindPUBLIC: { + 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 DTDNotationKindSYSTEM: { + DTDNotationDecl notation = (DTDNotationDecl) curr; + notation.setKind(scanner.getTokenOffset(), scanner.getTokenEnd()); + break; + } - case DTDNotationSystemId: { - DTDNotationDecl notation = (DTDNotationDecl) curr; - notation.setSystemId(scanner.getTokenOffset(), scanner.getTokenEnd()); - break; - } + case DTDNotationPublicId: { + DTDNotationDecl notation = (DTDNotationDecl) curr; + notation.setPublicId(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.setClosed(true); + 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; } - break; - } - - case DTDEndDoctypeTag: { - ((DOMDocumentType) curr).end = scanner.getTokenEnd(); + curr.end = scanner.getTokenEnd(); curr.setClosed(true); curr = curr.parent; - break; } + break; + } - case DTDUnrecognizedParameters: { - DTDDeclNode node = (DTDDeclNode) curr; - node.setUnrecognized(scanner.getTokenOffset(), ((XMLScanner) scanner).getLastNonWhitespaceOffset()); - break; - } + case DTDEndDoctypeTag: { + ((DOMDocumentType) curr).end = scanner.getTokenEnd(); + curr.setClosed(true); + curr = curr.parent; + break; + } + + case DTDUnrecognizedParameters: { + DTDDeclNode node = (DTDDeclNode) curr; + node.setUnrecognized(scanner.getTokenOffset(), ((XMLScanner) scanner).getLastNonWhitespaceOffset()); + break; + } - default: + default: } token = scanner.scan(); } @@ -694,8 +682,4 @@ public DOMDocument parse(TextDocument document, URIResolverExtensionManager reso return xmlDocument; } - private static boolean isEmptyElement(String tag) { - return false; - } - } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMProcessingInstruction.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMProcessingInstruction.java index 1463e8809..1a16d5faf 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMProcessingInstruction.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMProcessingInstruction.java @@ -21,7 +21,7 @@ public class DOMProcessingInstruction extends DOMCharacterData implements org.w3c.dom.ProcessingInstruction { boolean startTagClose; - String target; + CharSequence target; boolean prolog = false; boolean processingInstruction = false; int startContent; @@ -86,7 +86,7 @@ public String getNodeName() { */ @Override public String getTarget() { - return target; + return target.toString(); } /* diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMText.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMText.java index ba408bc89..618f1befe 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMText.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMText.java @@ -62,7 +62,7 @@ public String getWholeText() { */ @Override public boolean isElementContentWhitespace() { - String text = getOwnerDocument().getOwnerDocument().getText(); + CharSequence text = getOwnerDocument().getOwnerDocument().getTextSequence(); return StringUtils.isWhitespace(text, getStart(), getEnd()); } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DTDElementDecl.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DTDElementDecl.java index a6f78d6d1..74df5eaa1 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DTDElementDecl.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DTDElementDecl.java @@ -101,7 +101,7 @@ public DTDDeclParameter getParameterAt(int offset) { return null; } // We are after the wordStart offset and ends at wordEnd * matches the given searchName */ - private static boolean isMatchName(String searchWord, String text, int wordStart, int wordEnd) { + private static boolean isMatchName(String searchWord, CharSequence text, int wordStart, int wordEnd) { int length = wordEnd - wordStart; if (searchWord.length() != length) { return false; diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/parser/MultiLineStream.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/parser/MultiLineStream.java index 4af050409..5669fbb5d 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/parser/MultiLineStream.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/parser/MultiLineStream.java @@ -43,12 +43,12 @@ public class MultiLineStream { return ch == _WSP || ch == _TAB || ch == _NWL || ch == _LFD || ch == _CAR; }; - private final String source; + private final CharSequence source; private final int len; private int position; private final Map regexpCache; - public MultiLineStream(String source, int position) { + public MultiLineStream(CharSequence source, int position) { this.source = source; this.len = source.length(); this.position = position; @@ -59,7 +59,7 @@ public boolean eos() { return this.len <= this.position; } - public String getSource() { + public CharSequence getSource() { return this.source; } @@ -103,7 +103,7 @@ public int peekChar(int n) { if (pos >= len) { return -1; } - return this.source.codePointAt(pos); + return this.source.charAt(pos); } /** @@ -115,7 +115,7 @@ public int peekCharAtOffset(int offset) { if (offset >= len || offset < 0) { return -1; } - return this.source.codePointAt(offset); + return this.source.charAt(offset); } public boolean advanceIfChar(int ch) { diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/parser/Scanner.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/parser/Scanner.java index 8513acfa0..2bd72ebd0 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/parser/Scanner.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/parser/Scanner.java @@ -43,7 +43,7 @@ public interface Scanner { */ int getTokenEnd(); - String getTokenText(); + CharSequence getTokenText(); String getTokenError(); diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/parser/XMLScanner.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/parser/XMLScanner.java index 377c42bcf..475f49b7b 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/parser/XMLScanner.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/parser/XMLScanner.java @@ -139,7 +139,7 @@ public class XMLScanner implements Scanner { boolean isInitialAttlistDeclCompleted = false; private int nbBraceOpened; - public XMLScanner(String input, int initialOffset, ScannerState initialState, boolean isDTDFile) { + public XMLScanner(CharSequence input, int initialOffset, ScannerState initialState, boolean isDTDFile) { stream = new MultiLineStream(input, initialOffset); state = initialState; tokenOffset = 0; @@ -257,7 +257,7 @@ TokenType internalScan() { return finishToken(offset, TokenType.PIEnd); } if (stream.advanceUntilAnyOfChars(END_WS_OR_PROLOG_PATTERN) || stream.eos()) { // \n or \r or ' ' or '?' - String name = getTokenTextFromOffset(offset); + CharSequence name = getTokenTextFromOffset(offset); if (PROLOG_NAME_OPTIONS.matcher(name).matches()) { // name eg: xml state = ScannerState.WithinTag; return finishToken(offset, TokenType.PrologName); @@ -1030,8 +1030,8 @@ public int getTokenEnd() { } @Override - public String getTokenText() { - return stream.getSource().substring(tokenOffset, stream.pos()); + public CharSequence getTokenText() { + return stream.getSource().subSequence(tokenOffset, stream.pos()); } @Override @@ -1049,32 +1049,32 @@ public String getTokenError() { return tokenError; } - public String getTokenTextFromOffset(int offset) { - return stream.getSource().substring(offset, stream.pos()); + public CharSequence getTokenTextFromOffset(int offset) { + return stream.getSource().subSequence(offset, stream.pos()); } - public static Scanner createScanner(String input) { + public static Scanner createScanner(CharSequence input) { return createScanner(input, false); } - public static Scanner createScanner(String input, boolean isDTD) { + public static Scanner createScanner(CharSequence input, boolean isDTD) { return createScanner(input, 0, isDTD); } - public static Scanner createScanner(String input, int initialOffset) { + public static Scanner createScanner(CharSequence input, int initialOffset) { return createScanner(input, initialOffset, false); } - public static Scanner createScanner(String input, int initialOffset, boolean isDTDFile) { + public static Scanner createScanner(CharSequence input, int initialOffset, boolean isDTDFile) { return createScanner(input, initialOffset, isDTDFile ? ScannerState.DTDWithinContent : ScannerState.WithinContent, isDTDFile); } - public static Scanner createScanner(String input, int initialOffset, ScannerState initialState) { + public static Scanner createScanner(CharSequence input, int initialOffset, ScannerState initialState) { return new XMLScanner(input, initialOffset, initialState, false); } - public static Scanner createScanner(String input, int initialOffset, ScannerState initialState, boolean isDTDFile) { + public static Scanner createScanner(CharSequence input, int initialOffset, ScannerState initialState, boolean isDTDFile) { return new XMLScanner(input, initialOffset, initialState, isDTDFile); } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/ContentModelCompletionParticipant.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/ContentModelCompletionParticipant.java index fcb689172..a21df0df2 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/ContentModelCompletionParticipant.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/ContentModelCompletionParticipant.java @@ -240,7 +240,7 @@ private static void addTagName(NodeList list, Set tags, ICompletionReque CompletionItem item = new CompletionItem(tagName); item.setKind(CompletionItemKind.Property); item.setFilterText(request.getFilterForStartTagName(tagName)); - String xml = elt.getOwnerDocument().getText().substring(elt.getStart(), elt.getEnd()); + String xml = elt.getOwnerDocument().getTextSequence().subSequence(elt.getStart(), elt.getEnd()).toString(); item.setTextEdit(Either.forLeft(new TextEdit(request.getReplaceRange(), xml))); response.addCompletionItem(item); tags.add(item.getLabel()); @@ -456,7 +456,7 @@ public void onXMLContent(ICompletionRequest request, ICompletionResponse respons end = document.positionAt(endOffset); } int completionOffset = request.getOffset(); - String tokenStart = StringUtils.getWhitespaces(document.getText(), startOffset, + String tokenStart = StringUtils.getWhitespaces(document.getTextSequence(), startOffset, completionOffset); Range fullRange = new Range(start, end); values.forEach(value -> { diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/XMLSyntaxErrorCode.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/XMLSyntaxErrorCode.java index 13fd86b95..7badfe548 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/XMLSyntaxErrorCode.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/XMLSyntaxErrorCode.java @@ -12,6 +12,7 @@ */ package org.eclipse.lemminx.extensions.contentmodel.participants; +import static org.eclipse.lemminx.commons.text.CharSequenceUtils.indexOf; import static org.eclipse.lemminx.utils.StringUtils.getString; import static org.eclipse.lemminx.utils.XMLPositionUtility.selectCurrentTagOffset; @@ -26,7 +27,6 @@ import org.eclipse.lemminx.dom.DOMElement; import org.eclipse.lemminx.dom.DOMNode; import org.eclipse.lemminx.dom.DTDDeclNode; -import org.eclipse.lemminx.dom.DTDDeclParameter; import org.eclipse.lemminx.extensions.contentmodel.participants.codeactions.ETagRequiredCodeAction; import org.eclipse.lemminx.extensions.contentmodel.participants.codeactions.ETagUnterminatedCodeAction; import org.eclipse.lemminx.extensions.contentmodel.participants.codeactions.ElementUnterminatedCodeAction; @@ -191,7 +191,7 @@ public static Range toLSPRange(XMLLocator location, XMLSyntaxErrorCode code, Obj * * <-- error on idinstitut which must be quoted. - String parameterName = getString(arguments[1] /* idinstitut*/ ); + // ex : <-- error on idinstitut + // which must be quoted. + String parameterName = getString(arguments[1] /* idinstitut */ ); return XMLPositionUtility.selectParameterNameFromGivenName(parameterName, (DTDDeclNode) node); } // ex : <-- error on value which must be quoted. @@ -328,7 +329,7 @@ public static Range toLSPRange(XMLLocator location, XMLSyntaxErrorCode code, Obj * @return the offset of the first character from the left offset which is not a * whitespace. */ - private static int removeLeftSpaces(final int initialOffset, String text) { + private static int removeLeftSpaces(final int initialOffset, CharSequence text) { int offset = initialOffset; if (offset >= text.length()) { return text.length(); @@ -369,7 +370,7 @@ private static int removeLeftSpaces(final int initialOffset, String text) { * @return the proper range from the given node to the given offset. */ private static Range getRangeFromStartNodeToOffset(DOMNode fromNode, int toOffset, DOMDocument document) { - int endOffset = removeLeftSpaces(toOffset, document.getText()); + int endOffset = removeLeftSpaces(toOffset, document.getTextSequence()); int startOffset = fromNode.getStart(); if (fromNode.isElement()) { // The from node is a DOM element, adjust end and start offset @@ -427,4 +428,5 @@ public static void registerCodeActionParticipants(Map codeActions) throws BadLocationException { // Here start tag element is not closed with '>'. - String text = document.getText(); + CharSequence text = document.getTextSequence(); int closeAngleBracketOffset = element.getUnclosedStartTagCloseOffset(); final Position closeAngleBracketPosition = document.positionAt(closeAngleBracketOffset); if (!element.hasEndTag()) { @@ -150,7 +150,7 @@ private void doCodeActionsForStartTagUnclosed(DOMElement element, DOMDocument do private void doCodeActionsForStartTagClosed(DOMElement element, DOMDocument document, Range diagnosticRange, Diagnostic diagnostic, List codeActions) throws BadLocationException { // Here start tag element is closed with '>'. - String text = document.getText(); + CharSequence text = document.getTextSequence(); if (!element.hasEndTag()) { // The element has no an end tag // ex : @@ -261,10 +261,10 @@ private static CodeAction insertGreaterThanCharacterCodeAction(DOMDocument docum */ private static CodeAction removeTagCodeAction(DOMElement element, DOMDocument document, Diagnostic diagnostic) throws BadLocationException { - String text = document.getText(); + CharSequence text = document.getTextSequence(); Position startPosition = document.positionAt(element.getStart()); Position endPosition = document.positionAt(element.getEnd()); - String contentToRemove = text.substring(element.getStart(), element.getEnd()); + String contentToRemove = text.subSequence(element.getStart(), element.getEnd()).toString(); CodeAction removeAction = CodeActionFactory.remove("Remove '" + contentToRemove + "'", new Range(startPosition, endPosition), document.getTextDocument(), diagnostic); return removeAction; @@ -316,7 +316,7 @@ private static boolean hasElements(DOMElement element) { return false; } - private static boolean isCharAt(String text, int offset, char ch) { + private static boolean isCharAt(CharSequence text, int offset, char ch) { if (text.length() <= offset) { return false; } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/codeactions/DownloadDisabledResourceCodeAction.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/codeactions/DownloadDisabledResourceCodeAction.java index ae19c8d74..7de3ca24f 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/codeactions/DownloadDisabledResourceCodeAction.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/codeactions/DownloadDisabledResourceCodeAction.java @@ -53,7 +53,7 @@ public void doCodeAction(ICodeActionRequest request, List codeAction Range diagnosticRange = diagnostic.getRange(); int start = document.offsetAt(diagnosticRange.getStart()); int end = document.offsetAt(diagnosticRange.getEnd()); - String url = document.getText().substring(start, end); + String url = document.getTextSequence().subSequence(start, end).toString(); String title = MessageFormat.format(FORCE_DOWNLOAD_TITLE, url); CodeAction codeAction = new CodeAction(title); diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/codeactions/EntityNotDeclaredCodeAction.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/codeactions/EntityNotDeclaredCodeAction.java index 9c712ee32..71cbd65b2 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/codeactions/EntityNotDeclaredCodeAction.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/codeactions/EntityNotDeclaredCodeAction.java @@ -224,7 +224,7 @@ private Position getEntityInsertPosition(DOMDocument document) throws BadLocatio */ private static String getEntityName(Diagnostic diagnostic, DOMDocument doc) throws BadLocationException { Range range = diagnostic.getRange(); - String name = doc.getText().substring(doc.offsetAt(range.getStart()), doc.offsetAt(range.getEnd())); + String name = doc.getTextSequence().subSequence(doc.offsetAt(range.getStart()), doc.offsetAt(range.getEnd())).toString(); String removedAmpAndSemiColon = name.substring(1, name.length() - 1); if (!diagnostic.getMessage().contains("\"" + removedAmpAndSemiColon + "\"")) { return null; diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/codeactions/FixMissingSpaceCodeAction.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/codeactions/FixMissingSpaceCodeAction.java index 238efe0f0..175cf7b4e 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/codeactions/FixMissingSpaceCodeAction.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/codeactions/FixMissingSpaceCodeAction.java @@ -35,8 +35,8 @@ public void doCodeAction(ICodeActionRequest request, List codeAction try { int startOffset = document.offsetAt(diagnosticRange.getStart()); int endOffset = document.offsetAt(diagnosticRange.getEnd()); - String text = document.getText(); - String value = text.substring(startOffset, endOffset); + CharSequence text = document.getTextSequence(); + String value = text.subSequence(startOffset, endOffset).toString(); codeActions.add(CodeActionFactory.insert("Add space after '" + value + "'", diagnosticRange.getEnd(), " ", document.getTextDocument(), diagnostic)); } catch (BadLocationException | IndexOutOfBoundsException e) { diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/dtd/participants/diagnostics/DTDValidator.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/dtd/participants/diagnostics/DTDValidator.java index 278e6b09a..14af1dd6f 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/dtd/participants/diagnostics/DTDValidator.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/dtd/participants/diagnostics/DTDValidator.java @@ -14,7 +14,6 @@ import java.io.IOException; import java.io.Reader; -import java.io.StringReader; import java.util.HashMap; import java.util.List; import java.util.concurrent.CancellationException; @@ -24,6 +23,7 @@ import org.apache.xerces.impl.dtd.XMLDTDLoader; import org.apache.xerces.xni.parser.XMLEntityResolver; import org.apache.xerces.xni.parser.XMLInputSource; +import org.eclipse.lemminx.commons.text.CharSequenceUtils; import org.eclipse.lemminx.dom.DOMDocument; import org.eclipse.lemminx.extensions.contentmodel.model.ContentModelManager; import org.eclipse.lemminx.extensions.contentmodel.participants.diagnostics.LSPErrorReporterForXML; @@ -52,10 +52,10 @@ public static void doDiagnostics(DOMDocument document, XMLEntityResolver entityR validationSettings); XMLDTDLoader loader = new LSPXML11DTDProcessor(entityManager, reporterForXML, entityResolver); - String content = document.getText(); + CharSequence content = document.getTextSequence(); String uri = document.getDocumentURI(); - Reader inputStream = new StringReader(content); + Reader inputStream = CharSequenceUtils.newReader(content); XMLInputSource source = new XMLInputSource(null, uri, uri, inputStream, null); loader.loadGrammar(source); } catch (IOException | CancellationException exception) { diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/participants/FilePathCompletionParticipant.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/participants/FilePathCompletionParticipant.java index 9aed9f3b6..e3d60ae0b 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/participants/FilePathCompletionParticipant.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/participants/FilePathCompletionParticipant.java @@ -216,7 +216,7 @@ private static void addFileCompletionItems(DOMDocument xmlDocument, int startOff // ex: // base dir is equals for instance to C://path/to Character separator = expression != null ? expression.getSeparator() : null; - FilePathCompletionResult result = FilePathCompletionResult.create(xmlDocument.getText(), + FilePathCompletionResult result = FilePathCompletionResult.create(xmlDocument.getTextSequence(), xmlDocument.getDocumentURI(), startOffset, endOffset, completionOffset, separator); Path baseDir = result.getBaseDir(); if (baseDir == null) { diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/participants/FilePathCompletionResult.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/participants/FilePathCompletionResult.java index 174e55b8b..ece5936b8 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/participants/FilePathCompletionResult.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/filepath/participants/FilePathCompletionResult.java @@ -88,7 +88,7 @@ public Path getBaseDir() { * otherwise. * @return the file path completion result. */ - public static FilePathCompletionResult create(String content, String fileUri, int startNodeOffset, + public static FilePathCompletionResult create(CharSequence content, String fileUri, int startNodeOffset, int endNodeOffset, int completionOffset, Character separator) { boolean isMultiFilePath = separator != null; Predicate isStartValidChar = isStartValidCharForSimplePath; @@ -118,10 +118,10 @@ public static FilePathCompletionResult create(String content, String fileUri, in return new FilePathCompletionResult(startPathOffset, endPathOffset, baseDir); } - private static Path getBaseDir(String content, String fileUri, int start, int end) { + private static Path getBaseDir(CharSequence content, String fileUri, int start, int end) { if (end > start) { // ex : - String basePath = content.substring(start, end); + String basePath = content.subSequence(start, end).toString(); if (!hasPathBeginning(basePath)) { // Try to returns the absolute path // Ex basePath= diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/search/SearchNode.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/search/SearchNode.java index 6b16adb4d..d9a0e988c 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/search/SearchNode.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/search/SearchNode.java @@ -99,7 +99,7 @@ public String getValue(String forcedPrefix) { if (forcedPrefix != null) { value.append(forcedPrefix); } - String text = getOwnerDocument().getText(); + CharSequence text = getOwnerDocument().getTextSequence(); for (int i = getStart(); i < getEnd(); i++) { value.append(text.charAt(i)); } @@ -122,7 +122,7 @@ public String getPrefix() { public boolean matchesValue(SearchNode searchNode) { int fromStart = getStart(); int fromEnd = getEnd(); - String fromText = getOwnerDocument().getText(); + CharSequence fromText = getOwnerDocument().getTextSequence(); if (direction == Direction.FROM) { int adjust = adjustWithPrefix(this); if (adjust == -1) { @@ -132,7 +132,7 @@ public boolean matchesValue(SearchNode searchNode) { } int toStart = searchNode.getStart(); int toEnd = searchNode.getEnd(); - String toText = searchNode.getOwnerDocument().getText(); + CharSequence toText = searchNode.getOwnerDocument().getTextSequence(); if (direction == Direction.TO) { int adjust = adjustWithPrefix(searchNode); if (adjust == -1) { @@ -270,7 +270,7 @@ private boolean isValidPrefix() { if (prefix.length() > (end - start)) { return false; } - String text = node.getOwnerDocument().getText(); + CharSequence text = node.getOwnerDocument().getTextSequence(); for (int i = 0; i < prefix.length(); i++) { if (text.charAt(start + i) != prefix.charAt(i)) { return false; @@ -282,8 +282,8 @@ private boolean isValidPrefix() { @Override public String toString() { StringBuilder result = new StringBuilder(); - String text = node.getOwnerDocument().getText(); - result.append(text.substring(start, end)); + CharSequence text = node.getOwnerDocument().getTextSequence(); + result.append(text.subSequence(start, end)); result.append(direction == Direction.FROM ? " -->" : " <--"); return result.toString(); } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/search/SearchNodeFactory.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/search/SearchNodeFactory.java index d2fe34340..55cfe934e 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/search/SearchNodeFactory.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/search/SearchNodeFactory.java @@ -55,8 +55,7 @@ public class SearchNodeFactory { * * @return all search node of the given DOM node. */ - public static List findSearchNodes(DOMNode node, String prefix, boolean multiple, - Direction direction) { + public static List findSearchNodes(DOMNode node, String prefix, boolean multiple, Direction direction) { int startNode = getStartNode(node); if (startNode == -1) { return Collections.emptyList(); @@ -67,7 +66,7 @@ public static List findSearchNodes(DOMNode node, String prefix, bool } if (multiple) { - String text = node.getOwnerDocument().getText(); + CharSequence text = node.getOwnerDocument().getTextSequence(); List searchNodes = new ArrayList<>(); int itemStart = -1; for (int j = startNode; j < endNode; j++) { @@ -124,7 +123,7 @@ public static SearchNode getSearchNodeAt(DOMNode node, int offset, String prefix return null; } if (multiple) { - String text = node.getOwnerDocument().getText(); + CharSequence text = node.getOwnerDocument().getTextSequence(); if (offset != startNode) { int left = StringUtils.findStartWord(text, offset, startNode, NAME_PREDICATE); if (left != -1) { diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/participants/diagnostics/XSDValidator.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/participants/diagnostics/XSDValidator.java index 7090ea8f3..559362e5a 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/participants/diagnostics/XSDValidator.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/participants/diagnostics/XSDValidator.java @@ -14,7 +14,6 @@ import java.io.IOException; import java.io.Reader; -import java.io.StringReader; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -32,6 +31,7 @@ import org.apache.xerces.xni.parser.XMLEntityResolver; import org.apache.xerces.xni.parser.XMLInputSource; import org.apache.xerces.xni.parser.XMLParseException; +import org.eclipse.lemminx.commons.text.CharSequenceUtils; import org.eclipse.lemminx.dom.DOMDocument; import org.eclipse.lemminx.extensions.contentmodel.model.ContentModelManager; import org.eclipse.lemminx.extensions.contentmodel.settings.XMLValidationSettings; @@ -101,9 +101,9 @@ public static void doDiagnostics(DOMDocument document, XMLEntityResolver entityR grammarPreparser.setEntityResolver(entityResolver); } - String content = document.getText(); + CharSequence content = document.getTextSequence(); String uri = document.getDocumentURI(); - Reader inputStream = new StringReader(content); + Reader inputStream = CharSequenceUtils.newReader(content); XMLInputSource source = new XMLInputSource(null, uri, uri, inputStream, null); grammarPreparser.getLoader(XMLGrammarDescription.XML_SCHEMA); diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLCompletions.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLCompletions.java index f8ddb20bf..4aba7a99a 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLCompletions.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLCompletions.java @@ -95,11 +95,11 @@ public CompletionList doComplete(DOMDocument xmlDocument, Position position, Sha return completionResponse; } - String text = xmlDocument.getText(); + CharSequence text = xmlDocument.getTextSequence(); int offset = completionRequest.getOffset(); DOMNode node = completionRequest.getNode(); try { - if (text.isEmpty()) { + if (text.length() == 0) { // When XML document is empty, try to collect root element (from file // association) collectInsideContent(completionRequest, completionResponse, cancelChecker); @@ -107,7 +107,7 @@ public CompletionList doComplete(DOMDocument xmlDocument, Position position, Sha } Scanner scanner = XMLScanner.createScanner(text, node.getStart(), isInsideDTDContent(node, xmlDocument)); - String currentTag = ""; + CharSequence currentTag = ""; TokenType token = scanner.scan(); TokenType lastToken = null; while (token != TokenType.EOS && scanner.getTokenOffset() <= offset) { @@ -233,7 +233,7 @@ public CompletionList doComplete(DOMDocument xmlDocument, Position position, Sha case StartTagSelfClose: if (offset <= scanner.getTokenEnd()) { if (currentTag != null && currentTag.length() > 0 - && xmlDocument.getText().charAt(offset - 1) == '>') { // if the actual character + && xmlDocument.getTextSequence().charAt(offset - 1) == '>') { // if the actual character // typed // was // '>' @@ -358,7 +358,7 @@ public CompletionItem resolveCompletionItem(CompletionItem unresolved, DOMDocume */ private void collectSnippetSuggestions(CompletionRequest completionRequest, CompletionResponse completionResponse) { DOMDocument document = completionRequest.getXMLDocument(); - String text = document.getText(); + CharSequence text = document.getTextSequence(); int endExpr = completionRequest.getOffset(); // compute the from for search expression according to the node int fromSearchExpr = getExprLimitStart(completionRequest.getNode(), endExpr); @@ -407,7 +407,7 @@ private void collectSnippetSuggestions(CompletionRequest completionRequest, Comp } } - private static Integer getSuffixIndex(String text, String suffix, final int initOffset) { + private static Integer getSuffixIndex(CharSequence text, String suffix, final int initOffset) { int offset = initOffset; char ch = text.charAt(offset); // Try to search the first character which matches the suffix @@ -491,7 +491,7 @@ private static int getExprLimitStart(DOMNode currentNode, int offset) { return element.getStartTagCloseOffset() + 1; } - private static int getExprStart(String value, int from, int to) { + private static int getExprStart(CharSequence value, int from, int to) { if (to == 0) { return to; } @@ -556,8 +556,8 @@ public AutoCloseTagResponse doTagComplete(DOMDocument xmlDocument, Position posi if (offset <= 0) { return null; } - char c = xmlDocument.getText().charAt(offset - 1); - char cBefore = xmlDocument.getText().charAt(offset - 2); + char c = xmlDocument.getTextSequence().charAt(offset - 1); + char cBefore = xmlDocument.getTextSequence().charAt(offset - 2); String snippet = null; if (XMLPositionUtility.isInAttributeValue(xmlDocument, position)) { return null; @@ -598,7 +598,7 @@ public AutoCloseTagResponse doTagComplete(DOMDocument xmlDocument, Position posi return null; } } - String text = xmlDocument.getText(); + CharSequence text = xmlDocument.getTextSequence(); // After the slash is a close bracket boolean closeBracketAfterSlash = offset < text.length() ? text.charAt(offset) == '>' : false; @@ -684,7 +684,7 @@ private void collectOpenTagSuggestions(boolean hasOpenBracket, Range replaceRang CompletionRequest completionRequest, CompletionResponse completionResponse, CancelChecker cancelChecker) { try { DOMDocument document = completionRequest.getXMLDocument(); - String text = document.getText(); + CharSequence text = document.getTextSequence(); int tagNameEnd = document.offsetAt(replaceRange.getEnd()); int newOffset = getOffsetFollowedBy(text, tagNameEnd, ScannerState.WithinEndTag, TokenType.EndTagClose); if (newOffset != -1) { @@ -710,15 +710,15 @@ private void collectOpenTagSuggestions(boolean hasOpenBracket, Range replaceRang DOMElement parentNode = completionRequest.getParentElement(); if (parentNode != null && !parentNode.getOwnerDocument().hasGrammar()) { // no grammar, collect similar tags from the parent node - Set seenElements = new HashSet<>(); + Set seenElements = new HashSet<>(); if (parentNode != null && parentNode.isElement() && parentNode.hasChildNodes()) { parentNode.getChildren().forEach(node -> { DOMElement element = node.isElement() ? (DOMElement) node : null; - if (element == null || element.getTagName() == null - || seenElements.contains(element.getTagName())) { + if (element == null || !element.hasTagName() + || seenElements.contains(element.getTag())) { return; } - String tag = element.getTagName(); + CharSequence tag = element.getTag(); seenElements.add(tag); DOMElementCompletionItem item = new DOMElementCompletionItem(element, completionRequest); completionResponse.addCompletionItem(item); @@ -731,7 +731,7 @@ private void collectCloseTagSuggestions(int afterOpenBracket, boolean inOpenTag, CompletionRequest completionRequest, CompletionResponse completionResponse, CancelChecker cancelChecker) { try { Range range = getReplaceRange(afterOpenBracket, tagNameEnd, completionRequest); - String text = completionRequest.getXMLDocument().getText(); + CharSequence text = completionRequest.getXMLDocument().getTextSequence(); boolean hasCloseTag = isFollowedBy(text, tagNameEnd, ScannerState.WithinEndTag, TokenType.EndTagClose); collectCloseTagSuggestions(range, false, !hasCloseTag, inOpenTag, completionRequest, completionResponse); } catch (BadLocationException e) { @@ -742,7 +742,7 @@ private void collectCloseTagSuggestions(int afterOpenBracket, boolean inOpenTag, private void collectCloseTagSuggestions(Range range, boolean openEndTag, boolean closeEndTag, boolean inOpenTag, CompletionRequest completionRequest, CompletionResponse completionResponse) { try { - String text = completionRequest.getXMLDocument().getText(); + CharSequence text = completionRequest.getXMLDocument().getTextSequence(); DOMNode curr = completionRequest.getNode(); if (inOpenTag) { curr = curr.getParentNode(); // don't suggest the own tag, it's not yet open @@ -887,7 +887,7 @@ private void collectAttributeNameSuggestions(int nameStart, CompletionRequest co private void collectAttributeNameSuggestions(int nameStart, int nameEnd, CompletionRequest completionRequest, CompletionResponse completionResponse, CancelChecker cancelChecker) { int replaceEnd = completionRequest.getOffset(); - String text = completionRequest.getXMLDocument().getText(); + CharSequence text = completionRequest.getXMLDocument().getTextSequence(); while (replaceEnd < nameEnd && text.charAt(replaceEnd) != '<' && text.charAt(replaceEnd) != '?') { // < is a // valid // attribute @@ -928,7 +928,7 @@ private void collectAttributeValueSuggestions(int valueStart, int valueEnd, Comp boolean addQuotes = false; String valuePrefix; int offset = completionRequest.getOffset(); - String text = completionRequest.getXMLDocument().getText(); + CharSequence text = completionRequest.getXMLDocument().getTextSequence(); // Adjusts range to handle if quotations for the value exist if (offset > valueStart && offset <= valueEnd && StringUtils.isQuote(text.charAt(valueStart))) { @@ -940,13 +940,13 @@ private void collectAttributeValueSuggestions(int valueStart, int valueEnd, Comp valueContentEnd--; } valuePrefix = offset >= valueContentStart && offset <= valueContentEnd - ? text.substring(valueContentStart, offset) + ? text.subSequence(valueContentStart, offset).toString() : ""; valueStart = valueContentStart; valueEnd = valueContentEnd; addQuotes = false; } else { - valuePrefix = text.substring(valueStart, offset); + valuePrefix = text.subSequence(valueStart, offset).toString(); addQuotes = true; } @@ -992,11 +992,11 @@ private void collectAttributeValueSuggestions(int valueStart, int valueEnd, Comp private void collectDTDSystemIdSuggestions(int valueStart, int valueEnd, CompletionRequest completionRequest, CompletionResponse completionResponse, CancelChecker cancelChecker) { int offset = completionRequest.getOffset(); - String text = completionRequest.getXMLDocument().getText(); + CharSequence text = completionRequest.getXMLDocument().getTextSequence(); int valueContentStart = valueStart + 1; int valueContentEnd = valueEnd - 1; String valuePrefix = offset >= valueContentStart && offset <= valueContentEnd - ? text.substring(valueContentStart, offset) + ? text.subSequence(valueContentStart, offset).toString() : ""; Collection completionParticipants = getCompletionParticipants(); @@ -1046,7 +1046,7 @@ private Collection getCompletionParticipants() { return extensionsRegistry.getCompletionParticipants(); } - private static boolean isFollowedBy(String s, int offset, ScannerState intialState, TokenType expectedToken) { + private static boolean isFollowedBy(CharSequence s, int offset, ScannerState intialState, TokenType expectedToken) { return getOffsetFollowedBy(s, offset, intialState, expectedToken) != -1; } @@ -1060,7 +1060,7 @@ private static boolean isFollowedBy(String s, int offset, ScannerState intialSta * @param expectedToken * @return */ - public static int getOffsetFollowedBy(String s, int offset, ScannerState intialState, TokenType expectedToken) { + public static int getOffsetFollowedBy(CharSequence s, int offset, ScannerState intialState, TokenType expectedToken) { Scanner scanner = XMLScanner.createScanner(s, offset, intialState); TokenType token = scanner.scan(); while (token == TokenType.Whitespace) { @@ -1079,19 +1079,19 @@ private static Range getReplaceRange(int replaceStart, int replaceEnd, ICompleti return XMLPositionUtility.createRange(replaceStart, replaceEnd, document); } - private static String getLineIndent(int offset, String text) { + private static String getLineIndent(int offset, CharSequence text) { int start = offset; while (start > 0) { char ch = text.charAt(start - 1); if ("\n\r".indexOf(ch) >= 0) { - return text.substring(start, offset); + return text.subSequence(start, offset).toString(); } if (!isWhitespace(ch)) { return null; } start--; } - return text.substring(0, offset); + return text.subSequence(0, offset).toString(); } private boolean isEmptyElement(String tag) { diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLFoldings.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLFoldings.java index 65291bcfe..a6fd4bf2b 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLFoldings.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLFoldings.java @@ -53,9 +53,9 @@ class TagInfo { public final int startLine; - public final String tagName; + public final CharSequence tagName; - public TagInfo(int startLine, String tagName) { + public TagInfo(int startLine, CharSequence tagName) { this.startLine = startLine; this.tagName = tagName; } @@ -63,15 +63,15 @@ public TagInfo(int startLine, String tagName) { public List getFoldingRanges(TextDocument document, XMLFoldingSettings context, CancelChecker cancelChecker) { - Scanner scanner = XMLScanner.createScanner(document.getText()); + Scanner scanner = XMLScanner.createScanner(document.getTextSequence()); TokenType token = scanner.scan(); // Pre-allocate capacity based on document size (estimate: 1 folding per 500 chars) - int estimatedCapacity = Math.min(document.getText().length() / 500, 1000); + int estimatedCapacity = Math.min(document.getTextLength() / 500, 1000); List ranges = new ArrayList<>(estimatedCapacity); // Pre-allocate stack capacity (estimate: max nesting depth of 50) List stack = new ArrayList<>(50); - String lastTagName = null; + CharSequence lastTagName = null; int prevStart = -1; try { @@ -80,7 +80,7 @@ public List getFoldingRanges(TextDocument document, XMLFoldingSett switch (token) { case DTDStartDoctypeTag: case StartTag: { - String tagName = scanner.getTokenText(); + CharSequence tagName = scanner.getTokenText(); int startLine = document.positionAt(scanner.getTokenOffset()).getLine(); stack.add(new TagInfo(startLine, tagName)); lastTagName = tagName; @@ -124,14 +124,14 @@ public List getFoldingRanges(TextDocument document, XMLFoldingSett } case Comment: { int startLine = document.positionAt(scanner.getTokenOffset()).getLine(); - String text = scanner.getTokenText(); + CharSequence text = scanner.getTokenText(); Matcher m = REGION_PATTERN.matcher(text); if (m.find()) { if ("#region".equals(m.group().trim())) { // start pattern match stack.add(new TagInfo(startLine, "")); // empty tagName marks region } else { int i = stack.size() - 1; - while (i >= 0 && stack.get(i).tagName != null && !stack.get(i).tagName.isEmpty()) { + while (i >= 0 && stack.get(i).tagName != null && stack.get(i).tagName.length() != 0) { i--; } if (i >= 0) { diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLFormatter.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLFormatter.java index c9851e085..b1ecbc317 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLFormatter.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLFormatter.java @@ -89,7 +89,7 @@ public List format(DOMDocument xmlDocument, Range range, Sha private boolean shouldMergeEdits(List edits, DOMDocument xmlDocument) { // Merge if there are many edits (> 1000) or if the document is large (> 100KB) int editCount = edits != null ? edits.size() : 0; - int documentSize = xmlDocument.getTextDocument().getText().length(); + int documentSize = xmlDocument.getTextDocument().getTextSequence().length(); return editCount > 1000 || documentSize > 100_000; } @@ -103,7 +103,7 @@ private boolean shouldMergeEdits(List edits, DOMDocument xml private Range getFullDocumentRange(DOMDocument xmlDocument) throws BadLocationException { TextDocument textDocument = xmlDocument.getTextDocument(); Position start = new Position(0, 0); - Position end = textDocument.positionAt(textDocument.getText().length()); + Position end = textDocument.positionAt(textDocument.getTextSequence().length()); return new Range(start, end); } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLHover.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLHover.java index acacdb2a4..fdc606c42 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLHover.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLHover.java @@ -129,7 +129,7 @@ private Hover getTagHover(HoverRequest hoverRequest, Range tagRange, boolean ope } private Range getTagNameRange(TokenType tokenType, int startOffset, int offset, DOMDocument document) { - Scanner scanner = XMLScanner.createScanner(document.getText(), startOffset); + Scanner scanner = XMLScanner.createScanner(document.getTextSequence(), startOffset); TokenType token = scanner.scan(); while (token != TokenType.EOS && (scanner.getTokenEnd() < offset || scanner.getTokenEnd() == offset && token != tokenType)) { diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLLanguageService.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLLanguageService.java index 923b8d141..6fc2dbccf 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLLanguageService.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLLanguageService.java @@ -303,7 +303,7 @@ public AutoCloseTagResponse doAutoClose(DOMDocument xmlDocument, Position positi XMLCompletionSettings completionSettings, CancelChecker cancelChecker) { try { int offset = xmlDocument.offsetAt(position); - String text = xmlDocument.getText(); + CharSequence text = xmlDocument.getTextSequence(); if (offset > 0) { char c = text.charAt(offset - 1); if (c == '>' || c == '/') { diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/format/DOMCDATAFormatter.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/format/DOMCDATAFormatter.java index acbfaa8b2..d80c0ba2d 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/format/DOMCDATAFormatter.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/format/DOMCDATAFormatter.java @@ -28,7 +28,7 @@ public DOMCDATAFormatter(XMLFormatterDocument formatterDocument) { public void formatCDATASection(DOMCDATASection cDATANode, XMLFormattingConstraints parentConstraints, List edits) { - String text = formatterDocument.getText(); + CharSequence text = formatterDocument.getText(); int start = cDATANode.getStart(); int leftWhitespaceOffset = start > 0 ? start - 1 : 0; diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/format/DOMCommentFormatter.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/format/DOMCommentFormatter.java index 4492a2651..87aaa571d 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/format/DOMCommentFormatter.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/format/DOMCommentFormatter.java @@ -35,7 +35,7 @@ public void formatComment(DOMComment commentNode, XMLFormattingConstraints paren return; } - String text = formatterDocument.getText(); + CharSequence text = formatterDocument.getText(); int availableLineWidth = parentConstraints.getAvailableLineWidth(); int start = commentNode.getStart(); int leftWhitespaceOffset = start > 0 ? start - 1 : 0; diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/format/DOMDocTypeFormatter.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/format/DOMDocTypeFormatter.java index 1b292e6b6..a89c7bb03 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/format/DOMDocTypeFormatter.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/format/DOMDocTypeFormatter.java @@ -287,8 +287,8 @@ private static int getDocTypeIdEnd(DOMDocumentType docType) { private void replaceQuoteWithPreferred(DTDDeclNode nodeDecl, DTDDeclParameter parameter, List edits) { int paramStart = parameter.getStart(); int paramEnd = parameter.getEnd(); - if (StringUtils.isQuote(nodeDecl.getOwnerDocument().getText().charAt(paramStart)) - && StringUtils.isQuote(nodeDecl.getOwnerDocument().getText().charAt(paramEnd - 1))) { + if (StringUtils.isQuote(nodeDecl.getOwnerDocument().getTextSequence().charAt(paramStart)) + && StringUtils.isQuote(nodeDecl.getOwnerDocument().getTextSequence().charAt(paramEnd - 1))) { if (getEnforceQuoteStyle() == EnforceQuoteStyle.preferred) { formatterDocument.replaceQuoteWithPreferred(paramStart, paramStart + 1, edits); formatterDocument.replaceQuoteWithPreferred(paramEnd - 1, paramEnd, edits); diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/format/DOMTextFormatter.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/format/DOMTextFormatter.java index 9dec08ad0..a89e1b5b0 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/format/DOMTextFormatter.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/format/DOMTextFormatter.java @@ -38,7 +38,7 @@ public void formatText(DOMText textNode, XMLFormattingConstraints parentConstrai // Don't format the spacing in text for case of preserve empty content setting FormatElementCategory formatElementCategory = parentConstraints.getFormatElementCategory(); if (formatElementCategory == FormatElementCategory.PreserveSpace && isTrimTrailingWhitespace()) { - String text = formatterDocument.getText(); + CharSequence text = formatterDocument.getText(); int i = text.length() - 1; char curr = text.charAt(i); boolean removeSpaces = true; @@ -68,7 +68,7 @@ public void formatText(DOMText textNode, XMLFormattingConstraints parentConstrai } else if (formatElementCategory == FormatElementCategory.PreserveSpace) { return; } - String text = formatterDocument.getText(); + CharSequence text = formatterDocument.getText(); int availableLineWidth = parentConstraints.getAvailableLineWidth(); int indentLevel = parentConstraints.getIndentLevel(); boolean isMixedContent = formatElementCategory == FormatElementCategory.MixedContent; diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/format/XMLFormatterDocument.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/format/XMLFormatterDocument.java index f5163ddf8..33dd1b8ce 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/format/XMLFormatterDocument.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/format/XMLFormatterDocument.java @@ -145,7 +145,7 @@ public List format() throws BadLocationException { public List format(DOMDocument document, int start, int end) { // Pre-allocate list capacity based on document size to reduce reallocations // Estimate: 1 edit per 100 characters for typical XML formatting - int estimatedCapacity = Math.min(textDocument.getText().length() / 100, 10000); + int estimatedCapacity = Math.min(textDocument.getTextLength() / 100, 10000); List edits = new ArrayList<>(estimatedCapacity); // get initial document region @@ -179,7 +179,7 @@ public List format(DOMDocument document, int start, int end) } boolean insertFinalNewline = isInsertFinalNewline(); - String xml = textDocument.getText(); + CharSequence xml = textDocument.getTextSequence(); int endDocument = xml.length() - 1; if (isTrimFinalNewlines() && (end == -1 || endDocument < end)) { trimFinalNewlines(insertFinalNewline, edits); @@ -417,7 +417,7 @@ void replaceQuoteWithPreferred(int from, int to, List edits) { } public int adjustOffsetWithLeftWhitespaces(int leftLimit, int to) { - return TextEditUtils.adjustOffsetWithLeftWhitespaces(leftLimit, to, textDocument.getText()); + return TextEditUtils.adjustOffsetWithLeftWhitespaces(leftLimit, to, textDocument.getTextSequence()); } public int replaceLeftSpacesWithIndentation(int indentLevel, int leftLimit, int to, boolean addLineSeparator, @@ -457,7 +457,7 @@ public void replaceLeftSpacesWithIndentationPreservedNewLines(int spaceStart, in int indentLevel, List edits) { int preservedNewLines = getFormattingSettings().getPreservedNewlines(); int currentNewLineCount = XMLFormatterDocument.getExistingNewLineCount( - textDocument.getText(), spaceEnd, lineDelimiter); + textDocument.getTextSequence(), spaceEnd, lineDelimiter); if (currentNewLineCount > preservedNewLines) { replaceLeftSpacesWithIndentationWithMultiNewLines(indentLevel, spaceStart, spaceEnd, preservedNewLines + 1, edits); @@ -469,7 +469,7 @@ public void replaceLeftSpacesWithIndentationPreservedNewLines(int spaceStart, in } boolean hasLineBreak(int from, int to) { - String text = textDocument.getText(); + CharSequence text = textDocument.getTextSequence(); for (int i = from; i < to; i++) { char c = text.charAt(i); if (isLineSeparator(c)) { @@ -480,7 +480,7 @@ boolean hasLineBreak(int from, int to) { } public int getNormalizedLength(int from, int to) { - String text = textDocument.getText(); + CharSequence text = textDocument.getTextSequence(); int contentOffset = 0; for (int i = from; i < to; i++) { if (Character.isWhitespace(text.charAt(i)) && !Character.isWhitespace(text.charAt(i + 1))) { @@ -495,7 +495,7 @@ public int getNormalizedLength(int from, int to) { public int getOffsetWithPreserveLineBreaks(int from, int to, int tabSize, boolean isInsertSpaces) { int initialTo = to; - String text = textDocument.getText(); + CharSequence text = textDocument.getTextSequence(); for (int i = to; i > from; i--) { if (text.charAt(i) == '\t') { to -= tabSize; @@ -530,7 +530,7 @@ public int getOffsetWithPreserveLineBreaks(int from, int to, int tabSize, boolea // ------- Utilities method int updateLineWidthWithLastLine(DOMNode child, int availableLineWidth) { - String text = textDocument.getText(); + CharSequence text = textDocument.getTextSequence(); int lineWidth = availableLineWidth; int end = child.getEnd(); // Check if next char after the end of the DOM node is a new line feed. @@ -557,7 +557,7 @@ private static boolean isLineSeparator(char c) { } public int getLineBreakOffset(int startAttr, int start) { - String text = textDocument.getText(); + CharSequence text = textDocument.getTextSequence(); for (int i = startAttr; i < start; i++) { char c = text.charAt(i); if (isLineSeparator(c)) { @@ -736,7 +736,7 @@ private String getIndentSpacesWithOffsetSpaces(int spaceCount, boolean addLineSe } private void trimFinalNewlines(boolean insertFinalNewline, List edits) { - String xml = textDocument.getText(); + CharSequence xml = textDocument.getTextSequence(); int end = xml.length() - 1; int i = end; while (i >= 0 && isLineSeparator(xml.charAt(i))) { @@ -774,11 +774,11 @@ private void trimFinalNewlines(boolean insertFinalNewline, List edits) * @return the number of new lines in the whitespaces to the left of the given * offset. */ - public static int getExistingNewLineCount(String text, int offset, String delimiter) { + public static int getExistingNewLineCount(CharSequence text, int offset, String delimiter) { boolean delimiterHasTwoCharacters = delimiter.length() == 2; int newLineCounter = 0; for (int i = offset; i > 1; i--) { - String c; + CharSequence c; if (!Character.isWhitespace(text.charAt(i - 1))) { if (!delimiterHasTwoCharacters) { c = String.valueOf(text.charAt(i)); @@ -789,8 +789,8 @@ public static int getExistingNewLineCount(String text, int offset, String delimi return newLineCounter; } if (delimiterHasTwoCharacters) { - c = text.substring(i - 2, i); - if (delimiter.equals(c)) { + c = text.subSequence(i - 2, i); + if (c.equals(delimiter)) { newLineCounter++; i--; // skip the second char of the delimiter } @@ -848,8 +848,8 @@ String getLineDelimiter() { return lineDelimiter; } - String getText() { - return textDocument.getText(); + CharSequence getText() { + return textDocument.getTextSequence(); } public int getLineAtOffset(int offset) { diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/format/XMLFormatterDocumentOld.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/format/XMLFormatterDocumentOld.java index 2bc44b5f7..3745c1c1d 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/format/XMLFormatterDocumentOld.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/format/XMLFormatterDocumentOld.java @@ -84,7 +84,7 @@ public XMLFormatterDocumentOld(TextDocument textDocument, Range range, SharedSet * @throws BadLocationException */ public List format() throws BadLocationException { - this.fullDomDocument = DOMParser.getInstance().parse(textDocument.getText(), textDocument.getUri(), null, + this.fullDomDocument = DOMParser.getInstance().parse(textDocument.getTextSequence(), textDocument.getUri(), null, false); if (isRangeFormatting()) { @@ -115,8 +115,8 @@ private void setupRangeFormatting(Range range) throws BadLocationException { this.startOffset = this.textDocument.offsetAt(startPosition); this.endOffset = this.textDocument.offsetAt(endPosition); - String fullText = this.textDocument.getText(); - String rangeText = fullText.substring(this.startOffset, this.endOffset); + CharSequence fullText = this.textDocument.getTextSequence(); + String rangeText = fullText.subSequence(this.startOffset, this.endOffset).toString(); withinDTDContent = this.fullDomDocument.isWithinInternalDTD(startOffset); String uri = this.textDocument.getUri(); @@ -127,7 +127,7 @@ private void setupRangeFormatting(Range range) throws BadLocationException { if (containsTextWithinStartTag()) { adjustOffsetToStartTag(); - rangeText = fullText.substring(this.startOffset, this.endOffset); + rangeText = fullText.subSequence(this.startOffset, this.endOffset).toString(); this.rangeDomDocument = DOMParser.getInstance().parse(rangeText, uri, null, false); } @@ -167,7 +167,7 @@ private void adjustOffsetToStartTag() throws BadLocationException { private void setupFullFormatting(Range range) throws BadLocationException { this.startOffset = 0; - this.endOffset = textDocument.getText().length(); + this.endOffset = textDocument.getTextSequence().length(); this.rangeDomDocument = this.fullDomDocument; Position startPosition = textDocument.positionAt(startOffset); @@ -751,7 +751,7 @@ private List getFormatTextEdit() throws BadLocationException List edits = new ArrayList<>(); // check if format range reaches the end of the document - if (this.endOffset == this.textDocument.getText().length()) { + if (this.endOffset == this.textDocument.getTextSequence().length()) { if (this.sharedSettings.getFormattingSettings().isTrimFinalNewlines()) { this.xmlBuilder.trimFinalNewlines(); diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/snippets/SnippetContextUtils.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/snippets/SnippetContextUtils.java index 3c10c93a3..28e1ad463 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/snippets/SnippetContextUtils.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/snippets/SnippetContextUtils.java @@ -58,7 +58,7 @@ public static boolean canAcceptExpression(ICompletionRequest request) { if (element.isInInsideStartEndTag(offset)) { // | // - String text = request.getXMLDocument().getText(); + CharSequence text = request.getXMLDocument().getTextSequence(); if (text.charAt(offset - 1) == '/') { // -> should be ignore return false; @@ -78,7 +78,7 @@ public static boolean canAcceptExpression(ICompletionRequest request) { if (!element.hasEndTag()) { // | // should be ignore return false; diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/DOMUtils.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/DOMUtils.java index 80df91a8f..14292839f 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/DOMUtils.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/DOMUtils.java @@ -11,12 +11,12 @@ *******************************************************************************/ package org.eclipse.lemminx.utils; -import java.io.StringReader; import java.net.URL; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParserFactory; +import org.eclipse.lemminx.commons.text.CharSequenceUtils; import org.eclipse.lemminx.dom.DOMDocument; import org.eclipse.lemminx.dom.DOMElement; import org.eclipse.lemminx.dom.DOMNode; @@ -220,8 +220,7 @@ public static boolean isXSL(DOMDocument document) { * @return true if the given URI is a XSL and false otherwise. */ public static boolean isXSL(String uri) { - return uri != null - && (uri.endsWith(XSL_EXTENSION)); + return uri != null && (uri.endsWith(XSL_EXTENSION)); } /** @@ -281,10 +280,10 @@ public static boolean isDOMElement(DOMNode node, String tagName) { } public static InputSource createInputSource(DOMDocument document) { - String content = document.getText(); + CharSequence content = document.getTextSequence(); String uri = document.getDocumentURI(); InputSource inputSource = new InputSource(); - inputSource.setCharacterStream(new StringReader(content)); + inputSource.setCharacterStream(CharSequenceUtils.newReader(content)); inputSource.setSystemId(uri); return inputSource; } @@ -292,8 +291,9 @@ public static InputSource createInputSource(DOMDocument document) { /** * Returns false if the range is zero-length, and true otherwise. * - * @param range the range to check - * @param adjust true if the leading and trailing quotes should be removed before checking if it's zero-length, false otherwise + * @param range the range to check + * @param adjust true if the leading and trailing quotes should be removed + * before checking if it's zero-length, false otherwise * @return false if the range is zero-length, and true otherwise */ public static boolean isNonEmptyRange(DOMRange range, boolean adjust) { diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/StringUtils.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/StringUtils.java index 9147ada1a..34275b5c3 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/StringUtils.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/StringUtils.java @@ -42,11 +42,11 @@ public static boolean isQuote(char c) { return c == '\'' || c == '"'; } - public static boolean isWhitespace(String value, int index) { + public static boolean isWhitespace(CharSequence value, int index) { return isWhitespace(value, index, value.length()); } - public static boolean isWhitespace(String value, int index, int end) { + public static boolean isWhitespace(CharSequence value, int index, int end) { if (value == null) { return false; } @@ -134,7 +134,7 @@ public static String getStartWhitespaces(String lineText) { * @param text the text * @return the whitespaces from the given range start/end of the given text. */ - public static String getWhitespaces(String text, int start, int end) { + public static String getWhitespaces(CharSequence text, int start, int end) { StringBuilder whitespaces = new StringBuilder(); for (int i = start; i < end; i++) { char c = text.charAt(i); @@ -421,7 +421,7 @@ public static String getString(Object obj) { * @return the start word offset from the left of the given offset * and -1 if no word. */ - public static int findStartWord(String text, int offset, Predicate isValidChar) { + public static int findStartWord(CharSequence text, int offset, Predicate isValidChar) { return findStartWord(text, offset, 0, isValidChar); } @@ -437,7 +437,7 @@ public static int findStartWord(String text, int offset, Predicate is * @return the start word offset from the left of the given offset * to the given min and -1 if no word. */ - public static int findStartWord(String text, int offset, int min, Predicate isValidChar) { + public static int findStartWord(CharSequence text, int offset, int min, Predicate isValidChar) { if (offset < 0 || offset >= text.length()) { return -1; } @@ -460,7 +460,7 @@ public static int findStartWord(String text, int offset, int min, Predicateoffset * and -1 if no word. */ - public static int findEndWord(String text, int offset, Predicate isValidChar) { + public static int findEndWord(CharSequence text, int offset, Predicate isValidChar) { return findEndWord(text, offset, text.length(), isValidChar); } @@ -475,7 +475,7 @@ public static int findEndWord(String text, int offset, Predicate isVa * @return the start word offset from the right of the given offset * and -1 if no word. */ - public static int findEndWord(String text, int offset, int max, Predicate isValidChar) { + public static int findEndWord(CharSequence text, int offset, int max, Predicate isValidChar) { if (offset < 0 || offset >= text.length() || !isValidChar.test(text.charAt(offset))) { return -1; } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/TextEditUtils.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/TextEditUtils.java index 45bbcf07f..a9a4b6ff9 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/TextEditUtils.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/TextEditUtils.java @@ -53,7 +53,7 @@ public class TextEditUtils { * given range (from, to) of the given text document and null otherwise. */ public static TextEdit createTextEditIfNeeded(int from, int to, String expectedContent, TextDocument textDocument) { - String text = textDocument.getText(); + CharSequence text = textDocument.getTextSequence(); // Check if content from the range [from, to] is the same than expected content if (isMatchExpectedContent(from, to, expectedContent, text)) { @@ -85,7 +85,7 @@ public static TextEdit createTextEditIfNeeded(int from, int to, String expectedC * @return true if the given content from the range [from, to] of the given text * is the same than expected content and false otherwise. */ - private static boolean isMatchExpectedContent(int from, int to, String expectedContent, String text) { + private static boolean isMatchExpectedContent(int from, int to, String expectedContent, CharSequence text) { if (expectedContent.length() == to - from) { int j = 0; for (int i = from; i < to; i++) { @@ -102,7 +102,7 @@ private static boolean isMatchExpectedContent(int from, int to, String expectedC } public static String applyEdits(TextDocument document, List edits) throws BadLocationException { - String text = document.getText(); + CharSequence text = document.getTextSequence(); Collections.sort(edits /* .map(getWellformedEdit) */, (a, b) -> { int diff = a.getRange().getStart().getLine() - b.getRange().getStart().getLine(); if (diff == 0) { @@ -110,12 +110,12 @@ public static String applyEdits(TextDocument document, List } return diff; }); - + // Use StringBuilder for better memory efficiency, especially for large files // Pre-allocate capacity based on original text size to minimize reallocations StringBuilder result = new StringBuilder(text.length()); int lastModifiedOffset = 0; - + for (TextEdit e : edits) { int startOffset = document.offsetAt(e.getRange().getStart()); if (startOffset < lastModifiedOffset) { @@ -144,7 +144,7 @@ public static String applyEdits(TextDocument document, List * @return the offset of the first whitespace that's found in the given range * [leftLimit,to] from the left of the to, and leftLimit otherwise. */ - public static int adjustOffsetWithLeftWhitespaces(int leftLimit, int to, String text) { + public static int adjustOffsetWithLeftWhitespaces(int leftLimit, int to, CharSequence text) { if (to == 0) { return -1; } @@ -159,9 +159,10 @@ public static int adjustOffsetWithLeftWhitespaces(int leftLimit, int to, String } /** - * Creates a TextDocumentEdit object for the specified document and list of text edits + * Creates a TextDocumentEdit object for the specified document and list of text + * edits * - * @param document Document to be changed + * @param document Document to be changed * @param textEdits a list of text edit changes * @return A Text Dpcument Edit object */ @@ -170,7 +171,7 @@ public static TextDocumentEdit creatTextDocumentEdit(DOMDocument document, List< document.getDocumentURI(), document.getTextDocument().getVersion()); return new TextDocumentEdit(projectVersionedTextDocumentIdentifier, textEdits); } - + public static WorkspaceEdit createWorkspaceEdit(List> documentChanges) { return new WorkspaceEdit(documentChanges); } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/XMLPositionUtility.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/XMLPositionUtility.java index 39e315a04..427ef5dc1 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/XMLPositionUtility.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/XMLPositionUtility.java @@ -111,7 +111,7 @@ public static Range selectAttributeValue(DOMAttr attr) { /** * Returns the attribute value range and null otherwise. * - * @param attr the attribute. + * @param attr the attribute. * @param withoutQuote true if range must remove the quote and false otherwise. * @return the attribute value range and null otherwise. */ @@ -294,7 +294,7 @@ private static int adjustOffsetForAttribute(int offset, DOMDocument document) { // -> // -> // Remove spaces - String text = document.getText(); + CharSequence text = document.getTextSequence(); char c = text.charAt(offset); if (c == '>') { offset--; @@ -602,7 +602,7 @@ public static EntityReferenceRange selectEntityReference(int offset, DOMDocument */ public static EntityReferenceRange selectEntityReference(int offset, DOMDocument document, boolean endsWithSemicolon) { - String text = document.getText(); + CharSequence text = document.getTextSequence(); // Search '&' or '%' character on the left of the offset int entityReferenceStart = getEntityReferenceStartOffset(text, offset); if (entityReferenceStart == -1) { @@ -616,7 +616,8 @@ public static EntityReferenceRange selectEntityReference(int offset, DOMDocument } entityReferenceEnd = offset; } - String name = endsWithSemicolon ? document.getText().substring(entityReferenceStart + 1, entityReferenceEnd - 1) + String name = endsWithSemicolon + ? document.getTextSequence().subSequence(entityReferenceStart + 1, entityReferenceEnd - 1).toString() : null; return new EntityReferenceRange(name, createRange(entityReferenceStart, entityReferenceEnd, document)); } @@ -630,7 +631,7 @@ public static EntityReferenceRange selectEntityReference(int offset, DOMDocument * @return the start offset of the entity reference (ex : &am|p;) from the left * of the given offset and -1 if no entity reference. */ - public static int getEntityReferenceStartOffset(String text, int offset) { + public static int getEntityReferenceStartOffset(CharSequence text, int offset) { // adjust offset to get the left character of the offset offset--; if (offset < 0) { @@ -667,7 +668,7 @@ public static int getEntityReferenceStartOffset(String text, int offset) { * @return the end offset of the entity reference (ex : &am|p;) from the right * of the given offset and -1 if no entity reference. */ - public static int getEntityReferenceEndOffset(String text, int offset) { + public static int getEntityReferenceEndOffset(CharSequence text, int offset) { int endEntityOffset = StringUtils.findEndWord(text, offset, ENTITY_NAME_PREDICATE); if (endEntityOffset == -1) { return -1; @@ -688,7 +689,7 @@ public static Range selectFirstNonWhitespaceText(int offset, DOMDocument documen DOMCharacterData data = (DOMCharacterData) node; int start = data.getStartContent(); Integer end = null; - String text = document.getText(); + CharSequence text = document.getTextSequence(); for (int i = start; i < data.getEndContent(); i++) { char c = text.charAt(i); if (end == null) { @@ -923,7 +924,7 @@ public static Range getElementDeclMissingContentOrCategory(int offset, DOMDocume } return null; } - + public static Range selectParameterNameFromGivenName(String parameterName, DTDDeclNode declNode) { List parameters = declNode.getParameters(); for (DTDDeclParameter parameter : parameters) { @@ -1109,7 +1110,7 @@ public static boolean isBeforeOrEqual(Position pos1, Position pos2) { public static Range getTagNameRange(TokenType tokenType, int startOffset, DOMDocument xmlDocument) { - Scanner scanner = XMLScanner.createScanner(xmlDocument.getText(), startOffset); + Scanner scanner = XMLScanner.createScanner(xmlDocument.getTextSequence(), startOffset); TokenType token = scanner.scan(); while (token != TokenType.EOS && token != tokenType) { diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/XMLAssert.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/XMLAssert.java index 4ee6e1930..191f4839c 100644 --- a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/XMLAssert.java +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/XMLAssert.java @@ -1728,7 +1728,7 @@ public static void assertHighlights(XMLLanguageService languageService, String v assertEquals(expectedMatches[i], actualStartOffset); int actualEndOffset = document.offsetAt(highlight.getRange().getEnd()); assertEquals(expectedMatches[i] + (elementName != null ? elementName.length() : 0), actualEndOffset); - assertEquals(elementName, document.getText().substring(actualStartOffset, actualEndOffset).toLowerCase()); + assertEquals(elementName, document.getTextSequence().subSequence(actualStartOffset, actualEndOffset).toString().toLowerCase()); } } diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/commons/IncrementalParsingTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/commons/IncrementalParsingTest.java index 826f468fe..fbf98fcae 100644 --- a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/commons/IncrementalParsingTest.java +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/commons/IncrementalParsingTest.java @@ -57,7 +57,7 @@ public void testBasicChange() { document.update(changes); - assertEquals(expectedText, document.getText()); + assertEquals(expectedText, document.getTextSequence().toString()); } @@ -84,7 +84,7 @@ public void testBasicChangeWord() { document.update(changes); - assertEquals(expectedText, document.getText()); + assertEquals(expectedText, document.getTextSequence().toString()); } @Test @@ -110,7 +110,7 @@ public void testChangeReplaceRange() { document.update(changes); - assertEquals(expectedText, document.getText()); + assertEquals(expectedText, document.getTextSequence().toString()); } @Test @@ -136,7 +136,7 @@ public void testRangeLengthPreferredOverRangeEndPosition() { document.update(changes); - assertEquals(expectedText, document.getText()); + assertEquals(expectedText, document.getTextSequence().toString()); } // https://github.com/eclipse-lemminx/lemminx/issues/1674 @@ -159,7 +159,7 @@ public void testDeprecatedRangeLengthAllowsNull() { document.update(changes); - assertEquals(expectedText, document.getText()); + assertEquals(expectedText, document.getTextSequence().toString()); } @Test @@ -190,7 +190,7 @@ public void testBasicChangeMultipleChanges() { document.update(changes); - assertEquals(expectedText, document.getText()); + assertEquals(expectedText, document.getTextSequence().toString()); } @@ -222,7 +222,7 @@ public void testBasicChangeMultipleChangesReplaceRange() { document.update(changes); - assertEquals(expectedText, document.getText()); + assertEquals(expectedText, document.getTextSequence().toString()); } @@ -249,7 +249,7 @@ public void testBasicDeletionChange() { document.update(changes); - assertEquals(expectedText, document.getText()); + assertEquals(expectedText, document.getTextSequence().toString()); } @@ -280,7 +280,7 @@ public void testMultipleDeletionChanges() { document.update(changes); - assertEquals(expectedText, document.getText()); + assertEquals(expectedText, document.getTextSequence().toString()); } } \ No newline at end of file diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/commands/XMLValidationCommandTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/commands/XMLValidationCommandTest.java index 44b070f5d..968e1839a 100644 --- a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/commands/XMLValidationCommandTest.java +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/commands/XMLValidationCommandTest.java @@ -14,6 +14,7 @@ import static org.eclipse.lemminx.XMLAssert.c; import static org.eclipse.lemminx.XMLAssert.d; import static org.eclipse.lemminx.XMLAssert.pd; +import static org.eclipse.lemminx.commons.text.CharSequenceUtils.indexOf; import java.nio.file.Path; import java.util.Collections; @@ -372,7 +373,7 @@ public void validationAllFilesCommand() throws Exception { private static CompletionList completion(MockXMLLanguageServer languageServer, TextDocumentIdentifier xmlIdentifier) throws BadLocationException, InterruptedException, ExecutionException { DOMDocument document = languageServer.getDocument(xmlIdentifier.getUri()); - int offset = document.getText().indexOf(""); + int offset = indexOf(document.getTextSequence(), ""); Position position = document.positionAt(offset); CompletionParams completionParams = new CompletionParams(); completionParams.setTextDocument(xmlIdentifier); diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/performance/DOMParserPerformance.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/performance/DOMParserPerformance.java index 255773e2b..be6673745 100644 --- a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/performance/DOMParserPerformance.java +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/performance/DOMParserPerformance.java @@ -36,6 +36,7 @@ public static void main(String[] args) { while (true) { long start = System.currentTimeMillis(); DOMDocument xmlDocument = DOMParser.getInstance().parse(document, null); + //xmlDocument.dispose(); System.err.println("Parsed 'content.xml' with DOMParser in " + (System.currentTimeMillis() - start) + " ms."); } }