diff --git a/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java b/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java index e1b30285e..765703cae 100644 --- a/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java +++ b/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java @@ -25,6 +25,7 @@ import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemC import org.springframework.ide.vscode.commons.languageserver.reconcile.IReconcileEngine; import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemSeverity; import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblem; +import org.springframework.ide.vscode.commons.util.BadLocationException; import org.springframework.ide.vscode.commons.util.Futures; import org.springframework.ide.vscode.commons.util.text.TextDocument; @@ -157,14 +158,18 @@ public abstract class SimpleLanguageServer implements LanguageServer, LanguageCl @Override public void accept(ReconcileProblem problem) { - DiagnosticSeverity severity = getDiagnosticSeverity(problem); - if (severity!=null) { - Diagnostic d = new Diagnostic(); - d.setCode(problem.getCode()); - d.setMessage(problem.getMessage()); - d.setRange(doc.toRange(problem.getOffset(), problem.getLength())); - d.setSeverity(severity); - diagnostics.add(d); + try { + DiagnosticSeverity severity = getDiagnosticSeverity(problem); + if (severity!=null) { + Diagnostic d = new Diagnostic(); + d.setCode(problem.getCode()); + d.setMessage(problem.getMessage()); + d.setRange(doc.toRange(problem.getOffset(), problem.getLength())); + d.setSeverity(severity); + diagnostics.add(d); + } + } catch (BadLocationException e) { + LOG.log(Level.WARNING, "Invalid reconcile problem ignored", e); } } diff --git a/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleTextDocumentService.java b/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleTextDocumentService.java index 3e1305f4f..64234f694 100644 --- a/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleTextDocumentService.java +++ b/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleTextDocumentService.java @@ -8,6 +8,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; +import java.util.logging.Level; import java.util.logging.Logger; import org.eclipse.lsp4j.CodeActionParams; @@ -42,6 +43,8 @@ import org.eclipse.lsp4j.WorkspaceEdit; import org.eclipse.lsp4j.services.LanguageClient; import org.eclipse.lsp4j.services.TextDocumentService; import org.springframework.ide.vscode.commons.util.Assert; +import org.springframework.ide.vscode.commons.util.BadLocationException; +import org.springframework.ide.vscode.commons.util.ExceptionUtil; import org.springframework.ide.vscode.commons.util.Futures; import org.springframework.ide.vscode.commons.util.text.TextDocument; @@ -88,14 +91,18 @@ public class SimpleTextDocumentService implements TextDocumentService { @Override public final void didChange(DidChangeTextDocumentParams params) { - VersionedTextDocumentIdentifier docId = params.getTextDocument(); - String url = docId.getUri(); - if (url!=null) { - TextDocument doc = getOrCreateDocument(url); - for (TextDocumentContentChangeEvent change : params.getContentChanges()) { - doc.apply(change); - didChangeContent(doc, change); + try { + VersionedTextDocumentIdentifier docId = params.getTextDocument(); + String url = docId.getUri(); + if (url!=null) { + TextDocument doc = getOrCreateDocument(url); + for (TextDocumentContentChangeEvent change : params.getContentChanges()) { + doc.apply(change); + didChangeContent(doc, change); + } } + } catch (BadLocationException e) { + LOG.log(Level.SEVERE, ExceptionUtil.getMessage(e), e); } } diff --git a/vscode-extensions/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/text/IDocument.java b/vscode-extensions/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/text/IDocument.java index f87b179fa..a8600f9dd 100644 --- a/vscode-extensions/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/text/IDocument.java +++ b/vscode-extensions/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/text/IDocument.java @@ -12,10 +12,10 @@ public interface IDocument { int getNumberOfLines(); String getDefaultLineDelimiter(); char getChar(int offset) throws BadLocationException; - int getLineOfOffset(int offset); + int getLineOfOffset(int offset) throws BadLocationException; IRegion getLineInformation(int line); - int getLineOffset(int line); - void replace(int start, int len, String text); + int getLineOffset(int line) throws BadLocationException; + void replace(int start, int len, String text) throws BadLocationException; String textBetween(int start, int end) throws BadLocationException; } diff --git a/vscode-extensions/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/text/TextDocument.java b/vscode-extensions/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/text/TextDocument.java index 3d95e46a9..ea5c7e060 100644 --- a/vscode-extensions/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/text/TextDocument.java +++ b/vscode-extensions/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/text/TextDocument.java @@ -9,19 +9,16 @@ import org.eclipse.lsp4j.Position; import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.TextDocumentContentChangeEvent; import org.springframework.ide.vscode.commons.util.BadLocationException; +import org.springframework.ide.vscode.commons.util.text.linetracker.DefaultLineTracker; +import org.springframework.ide.vscode.commons.util.text.linetracker.ILineTracker; import javolution.text.Text; public class TextDocument implements IDocument { - //TODO: This representiation of 'document content' is simplistic and inefficient - // for large documents. - // Making any change, such as inserting a character a user typed, works by copying the String that represents the - // contents. This could really become problematic for largish-documents when there are frequent changes. - - Pattern NEWLINE = Pattern.compile("\\r|\\n|\\r\\n|\\n\\r"); - private int[] _lineStarts; - + ILineTracker lineTracker = new DefaultLineTracker(); + private static final Pattern NEWLINE = Pattern.compile("\\r|\\n|\\r\\n|\\n\\r"); + private final String uri; private Text text = new Text(""); @@ -32,7 +29,7 @@ public class TextDocument implements IDocument { private TextDocument(TextDocument other) { this.uri = other.uri; this.text = other.text; - this._lineStarts = other._lineStarts; //no need to reparse lines. + this.lineTracker.set(text.toString()); } @Override @@ -49,15 +46,16 @@ public class TextDocument implements IDocument { return text; } - public synchronized void setText(CharSequence text) { - this.text = Text.valueOf(text); - this._lineStarts = null; + public synchronized void setText(String text) { + this.text = new Text(text); + this.lineTracker.set(text); } - public void apply(TextDocumentContentChangeEvent change) { + + public void apply(TextDocumentContentChangeEvent change) throws BadLocationException { Range rng = change.getRange(); if (rng==null) { //full sync mode - setText(new Text(change.getText())); + setText(change.getText()); } else { int start = toOffset(rng.getStart()); int end = toOffset(rng.getEnd()); @@ -70,8 +68,8 @@ public class TextDocument implements IDocument { * TextDocument because it requires splitting document into lines to determine * line numbers from offsets. */ - public Range toRange(int offset, int length) { - int end = offset + length; + public Range toRange(int offset, int length) throws BadLocationException { + int end = Math.min(offset + length, getLength()); Range range = new Range(); range.setStart(toPosition(offset)); range.setEnd(toPosition(end)); @@ -81,22 +79,12 @@ public class TextDocument implements IDocument { /** * Determine the line-number a given offset (i.e. what line is the offset inside of?) */ - private int lineNumber(int offset) { - int[] lineStarts = lineStarts(); - // TODO Should really use binary search here for speed - int lineNumber = 0; - for (int i = 0; i < lineStarts.length; i++) { - if (lineStarts[i]<=offset) { - lineNumber = i; - } else { - return lineNumber; - } - } - return lineNumber; + private int lineNumber(int offset) throws BadLocationException { + return lineTracker.getLineNumberOfOffset(offset); } - public Position toPosition(int offset) { + public Position toPosition(int offset) throws BadLocationException { int line = lineNumber(offset); int startOfLine = startOfLine(line); int column = offset - startOfLine; @@ -106,37 +94,20 @@ public class TextDocument implements IDocument { return pos; } - private int startOfLine(int line) { - return lineStarts()[line]; - } - - private synchronized int[] lineStarts() { - if (_lineStarts==null) { - _lineStarts = parseLines(); - } - return _lineStarts; - } - - private int[] parseLines() { - List lineStarts = new ArrayList<>(); - lineStarts.add(0); - Matcher matcher = NEWLINE.matcher(getText()); - int pos = 0; - while (matcher.find(pos)) { - lineStarts.add(pos = matcher.end()); - } - int[] array = new int[lineStarts.size()]; - for (int i = 0; i < array.length; i++) { - array[i] = lineStarts.get(i); - } - return array; + private int startOfLine(int line) throws BadLocationException { + IRegion region = lineTracker.getLineInformation(line); + return region.getOffset(); } @Override public IRegion getLineInformationOfOffset(int offset) { - if (offset<=getLength()) { - int line = lineNumber(offset); - return getLineInformation(line); + try { + if (offset<=getLength()) { + int line = lineNumber(offset); + return getLineInformation(line); + } + } catch (BadLocationException e) { + //outside document. } return null; } @@ -157,7 +128,7 @@ public class TextDocument implements IDocument { @Override public int getNumberOfLines() { - return lineStarts().length; + return lineTracker.getNumberOfLines(); } @Override @@ -178,68 +149,38 @@ public class TextDocument implements IDocument { } @Override - public int getLineOfOffset(int offset) { - return lineNumber(offset); + public int getLineOfOffset(int offset) throws BadLocationException { + return lineTracker.getLineNumberOfOffset(offset); } @Override public IRegion getLineInformation(int line) { - int[] starts = lineStarts(); - if (line=starts.length) { - //no next line. Last line in the document - end = getLength(); - } else { - end = starts[line+1]; - //To behave like Eclipse IDocument we must strip off line delimiter from the end. - char c1 = getSafeChar(end-1); - if (c1=='\r' || c1=='\n') { - end--; - char c2 = getSafeChar(end-1); - if (c1!=c2 && (c2=='\r' || c2=='\n')) { - end--; - } - } - } - - int len = end - start; - if (len<0) { - len = 0; - } - return new Region(start, end-start); + try { + return lineTracker.getLineInformation(line); + } catch (BadLocationException e) { + //line doesn't exist } return null; } - private char getSafeChar(int ofs) { - try { - return getChar(ofs); - } catch (BadLocationException e) { - return 0; - } - } - @Override - public int getLineOffset(int line) { - return lineStarts()[line]; + public int getLineOffset(int line) throws BadLocationException { + return lineTracker.getLineOffset(line); } - public int toOffset(Position position) { - int line = position.getLine(); - int lineStart = lineStarts()[line]; + public int toOffset(Position position) throws BadLocationException { + IRegion region = lineTracker.getLineInformation(position.getLine()); + int lineStart = region.getOffset(); return lineStart + position.getCharacter(); } @Override - public synchronized void replace(int start, int len, String ins) { + public synchronized void replace(int start, int len, String ins) throws BadLocationException { int end = start+len; - setText(text + text = text .delete(start, end) - .insert(start, new Text(ins)) - ); + .insert(start, new Text(ins)); + lineTracker.replace(start, len, ins); } public synchronized TextDocument copy() {