// routine(s) called by the FileContentManager upon updating a file /// <summary> /// Attempts to compute an incremental update for the change specified by start, count and newText, and updates file accordingly. /// The given argument newText replaces the entire lines from start to (but not including) start + count. /// If the given change is null, then (only) the currently queued unprocessed changes are processed. /// Throws an ArgumentNullException if file is null. /// Any other exceptions should be throws (and caught, and possibly re-thrown) during the updating. /// </summary> internal static void UpdateScopeTacking(this FileContentManager file, TextDocumentContentChangeEvent change) { if (file == null) { throw new ArgumentNullException(nameof(file)); } /// <summary> /// Replaces the lines in the range [start, end] with those for the given text. /// </summary> void ComputeUpdate(int start, int end, string text) { QsCompilerError.Verify(start >= 0 && end >= start && end < file.NrLines(), "invalid range for update"); // since both LF and CR in VS cause a line break on their own, // we need to check if the change causes subequent CR LF to merge into a single line break if (text.StartsWith(Utils.LF) && start > 0 && file.GetLine(start - 1).Text.EndsWith(Utils.CR)) { text = file.GetLine(--start).Text + text; } // we need to check if the change causes the next line to merge with the (last) changed line if (end + 1 < file.NrLines() && !Utils.EndOfLine.Match(text).Success) { text = text + file.GetLine(++end).Text; } var newLines = Utils.SplitLines(text); var count = end - start + 1; // note that the last line in the file won't end with a line break, // and is hence only captured by SplitLines if it is not empty // -> we therefore manually add the last line in the file if it is empty if (newLines.Length == 0 || // the case if the file will be empty after the update (start + count == file.NrLines() && Utils.EndOfLine.Match(newLines.Last()).Success)) { newLines = newLines.Concat(new string[] { String.Empty }).ToArray(); } QsCompilerError.Verify(newLines.Any(), "should have at least one line to replace"); file.Update(start, count, newLines); } file.SyncRoot.EnterUpgradeableReadLock(); try { // process the currently queued changes if necessary if (file.DequeueUnprocessedChanges(out int start, out string text)) { ComputeUpdate(start, start, text); } // process the given change if necessary if (change != null) { ComputeUpdate(change.Range.Start.Line, change.Range.End.Line, Utils.GetTextChangedLines(file, change)); } } finally { file.SyncRoot.ExitUpgradeableReadLock(); } }
internal static void ApplyEdit(TextDocumentContentChangeEvent change, ref List <string> content) { if (!content.Any()) { throw new ArgumentException("the given content has to have at least on line"); } Assert.IsTrue(IsValidRange(change.Range) && change.Text != null); Assert.IsTrue(change.Range.End.Line < content.Count()); Assert.IsTrue(change.Range.Start.Character <= content[change.Range.Start.Line].Length); Assert.IsTrue(change.Range.End.Character <= content[change.Range.End.Line].Length); var(startLine, startChar) = (change.Range.Start.Line, change.Range.Start.Character); var(endLine, endChar) = (change.Range.End.Line, change.Range.End.Character); var newText = string.Concat(content[startLine].Substring(0, startChar), change.Text, content[endLine].Substring(endChar)); if (startLine > 0) { newText = content[--startLine] + newText; } if (endLine + 1 < content.Count) { newText = newText + content[++endLine]; } var lineChanges = Builder.SplitLines(newText); if (lineChanges.Length == 0 || (endLine + 1 == content.Count() && Builder.EndOfLine.Match(lineChanges.Last()).Success)) { lineChanges = lineChanges.Concat(new string[] { string.Empty }).ToArray(); } content.RemoveRange(startLine, endLine - startLine + 1); content.InsertRange(startLine, lineChanges); }
public static PapCommon.ScriptTextChange ToScriptTextChange(this TextDocumentContentChangeEvent changeEvent) { return(new PapCommon.ScriptTextChange() { Range = changeEvent.Range.ToRange(), RangeLength = changeEvent.RangeLength, Text = changeEvent.Text }); }
private static string ApplyTextChange(string text, TextDocumentContentChangeEvent change, CancellationToken cancellationToken) { if (change.Range == null) { throw new InvalidOperationException("the range of the change must not be null"); } int absoluteStart = change.Range.Start.ToAbsolutePosition(text, cancellationToken); int absoluteEnd = change.Range.End.ToAbsolutePosition(text, cancellationToken); return(text[..absoluteStart] + change.Text + text[absoluteEnd..]);
public override void OnDidSaveTextDocument(DidSaveTextDocumentParams parameters) { if (parameters.text != null) { DidChangeTextDocumentParams dctdp = new DidChangeTextDocumentParams(parameters.textDocument.uri); TextDocumentContentChangeEvent tdcce = new TextDocumentContentChangeEvent(); tdcce.text = parameters.text; dctdp.contentChanges = new TextDocumentContentChangeEvent[] { tdcce }; OnDidChangeTextDocument(dctdp); } }
private Position GetPositionAtEndOfAppliedChange(TextDocumentContentChangeEvent change) { var changeStart = change.Range.Start; var changeEof = change.Text.GetEofPosition(_cancellationToken); var characterOffset = changeEof.Character; if (changeEof.Line == 0) { characterOffset = changeStart.Character + changeEof.Character; } return(new Position(changeStart.Line + changeEof.Line, characterOffset)); }
private void Apply(TextDocumentItem document, TextDocumentContentChangeEvent ev) { if (ev.range != null) { var startPos = GetPosition(document, ev.range.start); var endPos = GetPosition(document, ev.range.end); document.text = document.text.Substring(0, startPos) + ev.text + document.text.Substring(endPos); } else { document.text = ev.text; } }
private void Apply(TextDocumentItem document, TextDocumentContentChangeEvent ev) { if (ev.range != null) { var startPos = GetPosition(document.text, (int)ev.range.start.line, (int)ev.range.start.character); var endPos = GetPosition(document.text, (int)ev.range.end.line, (int)ev.range.end.character); var newText = document.text.Substring(0, startPos) + ev.text + document.text.Substring(endPos); document.text = newText; } else { document.text = ev.text; } }
private static void Apply(TextDocumentItem document, TextDocumentContentChangeEvent ev) { if (ev.Range != null) { var startPos = GetPosition(document.Text, (int)ev.Range.Start.Line, (int)ev.Range.Start.Character); var endPos = GetPosition(document.Text, (int)ev.Range.End.Line, (int)ev.Range.End.Character); var newText = document.Text.Substring(0, startPos) + ev.Text + document.Text.Substring(endPos); document.Text = newText; } else { document.Text = ev.Text; } }
/// <summary> /// Return a string with the new content of the (entire) lines in the range [start, end] where start and end are /// the start and end line of the given change. /// </summary> /// <exception cref="ArgumentException">The range is invalid.</exception> /// <exception cref="ArgumentOutOfRangeException">The range is not contained in <paramref name="file"/>.</exception> internal static string GetTextChangedLines(FileContentManager file, TextDocumentContentChangeEvent change) { if (!file.ContainsRange(change.Range.ToQSharp())) { throw new ArgumentOutOfRangeException(nameof(change)); // range can be empty } var first = file.GetLine(change.Range.Start.Line).Text; var last = file.GetLine(change.Range.End.Line).Text; var prepend = first.Substring(0, change.Range.Start.Character); var append = last.Substring(change.Range.End.Character); return(string.Concat(prepend, change.Text, append)); }
public void SimpleTest(string expected) { var model = new TextDocumentContentChangeEvent { Range = new Range(new Position(1, 2), new Position(3, 4)), RangeLength = 12, Text = "abc" }; var result = Fixture.SerializeObject(model); result.Should().Be(expected); var deresult = new LspSerializer(ClientVersion.Lsp3).DeserializeObject <TextDocumentContentChangeEvent>(expected); deresult.Should().BeEquivalentTo(model, x => x.UsingStructuralRecordEquality()); }
private void Apply(TextDocument document, TextDocumentContentChangeEvent changeEvent) { if (changeEvent.Range != null) { int startPos = GetPosition(document.Text, changeEvent.Range.Start.Line, changeEvent.Range.Start.Character); int endPos = GetPosition(document.Text, changeEvent.Range.End.Line, changeEvent.Range.End.Character); string newText = document.Text.Substring(0, startPos) + changeEvent.Text + document.Text.Substring(endPos); document.Text = newText; } else { document.Text = changeEvent.Text; } }
public void SimpleTest(string expected) { var model = new TextDocumentContentChangeEvent() { Range = new Range(new Position(1, 2), new Position(3, 4)), RangeLength = 12, Text = "abc" }; var result = Fixture.SerializeObject(model); result.Should().Be(expected); var deresult = JsonConvert.DeserializeObject <TextDocumentContentChangeEvent>(expected); deresult.ShouldBeEquivalentTo(model); }
/// the last edit will always be a delete all internal TextDocumentContentChangeEvent[] MakeRandomEdits(int nrEdits, ref List <string> content, int expectedNrLines, bool withLanguageKeywords) { var edits = new TextDocumentContentChangeEvent[nrEdits]; if (nrEdits == 0) { return(edits); } for (var i = 0; i < edits.Length - 1; ++i) { edits[i] = this.GetRandomEdit(content, expectedNrLines, withLanguageKeywords); TestUtils.ApplyEdit(edits[i], ref content); } edits[edits.Length - 1] = DeleteAll(content); TestUtils.ApplyEdit(edits.Last(), ref content); return(edits); }
/// <summary> /// Return a string with the new content of the (entire) lines in the range [start, end] where start and end are the start and end line of the given change. /// Verifies that the given change is consistent with the given file - i.e. the range is a valid range in file, and the text is not null, and /// throws the correspoding exceptions if this is not the case. /// </summary> internal static string GetTextChangedLines(FileContentManager file, TextDocumentContentChangeEvent change) { if (!IsValidRange(change.Range, file)) { throw new ArgumentOutOfRangeException(nameof(change)); // range can be empty } if (change.Text == null) { throw new ArgumentNullException(nameof(change.Text), "the given text change is null"); } var first = file.GetLine(change.Range.Start.Line).Text; var last = file.GetLine(change.Range.End.Line).Text; var prepend = first.Substring(0, change.Range.Start.Character); var append = last.Substring(change.Range.End.Character); return(string.Concat(prepend, change.Text, append)); }
public void Patch(System.Uri uri, TextDocumentContentChangeEvent change) { if (change.Range is null) { // non-incremental/full buffer synchronization this.Put(uri, change.Text); return; } var buffer = _buffers.GetOrAdd(uri.ToString(), (key) => new StringBuilder()); var text = buffer.ToString(); int start = BufferPosition(text, change.Range.Start); if (start != -1) { buffer.Remove(start, change.RangeLength); buffer.Insert(start, change.Text); } }
public void ApplyContentChanges_SingleChange() { // Arrange var endpoint = new RazorDocumentSynchronizationEndpoint(Dispatcher, DocumentResolver, ProjectService, LoggerFactory); var sourceText = SourceText.From("Hello World"); var change = new TextDocumentContentChangeEvent() { Range = new Range(new Position(0, 5), new Position(0, 5)), RangeLength = 0, Text = "!" }; // Act var result = endpoint.ApplyContentChanges(new[] { change }, sourceText); // Assert var resultString = GetString(result); Assert.Equal("Hello! World", resultString); }
public void Empty_at_0_0() { TextDocumentContentChangeEvent change = new TextDocumentContentChangeEvent { Text = "", Range = new Range { Start = new Position { Line = 0, Character = 0 }, End = new Position { Line = 0, Character = 0 } }, RangeLength = 0 }; f.Apply(change); Assert.AreEqual(cleanSource, cleanSource); }
public void InsertOneCharAt_1_0() { TextDocumentContentChangeEvent change = new TextDocumentContentChangeEvent { Text = "a", Range = new Range { Start = new Position { Line = 1, Character = 0 }, End = new Position { Line = 1, Character = 0 } }, RangeLength = 0 }; f.Apply(change); Assert.AreEqual(cleanSource.Insert(2, "a"), f.Sourcecode); }
public void PressingRETURNat_15_15() { TextDocumentContentChangeEvent change = new TextDocumentContentChangeEvent { Text = "", Range = new Range { Start = new Position { Line = 15, Character = 14 }, End = new Position { Line = 15, Character = 15 } }, RangeLength = 1 }; f.Apply(change); Assert.AreEqual(cleanSource.Remove(228, 1), f.Sourcecode); }
public void InsertTextAt_15_15() { TextDocumentContentChangeEvent change = new TextDocumentContentChangeEvent { Text = "abc", Range = new Range { Start = new Position { Line = 15, Character = 15 }, End = new Position { Line = 15, Character = 15 } }, RangeLength = 0 }; f.Apply(change); Assert.AreEqual(cleanSource.Insert(229, "abc"), f.Sourcecode); }
public void RemovingLine_15_IncludingNewLine() { TextDocumentContentChangeEvent change = new TextDocumentContentChangeEvent { Text = "", Range = new Range { Start = new Position { Line = 15, Character = 0 }, End = new Position { Line = 16, Character = 0 } }, RangeLength = 37 }; f.Apply(change); Assert.AreEqual(cleanSource.Remove(214, 37), f.Sourcecode); }
public void RemovingRangeAt_15_15() { TextDocumentContentChangeEvent change = new TextDocumentContentChangeEvent { Text = "", Range = new Range { Start = new Position { Line = 15, Character = 15 }, End = new Position { Line = 15, Character = 20 } }, RangeLength = 5 }; f.Apply(change); Assert.AreEqual(cleanSource.Remove(229, 5), f.Sourcecode); }
public void RemovingLine_1() { TextDocumentContentChangeEvent change = new TextDocumentContentChangeEvent { Text = "", Range = new Range { Start = new Position { Line = 0, Character = 0 }, End = new Position { Line = 1, Character = 0 } }, RangeLength = 2 }; f.Apply(change); Assert.AreEqual(cleanSource.Remove(0, 2), f.Sourcecode); }
public void ReplaceLine_15() { TextDocumentContentChangeEvent change = new TextDocumentContentChangeEvent { Text = "baba", Range = new Range { Start = new Position { Line = 15, Character = 1 }, End = new Position { Line = 15, Character = 30 } }, RangeLength = 29 }; f.Apply(change); Assert.AreEqual(cleanSource.Remove(215, 29).Insert(215, "baba"), f.Sourcecode); }
public async Task Handle_DidChangeTextDocument_UpdatesDocument() { // Arrange var documentPath = "C:/path/to/document.cshtml"; var sourceText = SourceText.From("<p>"); var documentResolver = CreateDocumentResolver(documentPath, sourceText); var projectService = new Mock <RazorProjectService>(MockBehavior.Strict); projectService.Setup(service => service.UpdateDocument(It.IsAny <string>(), It.IsAny <SourceText>(), It.IsAny <long>())) .Callback <string, SourceText, long>((path, text, version) => { var resultString = GetString(text); Assert.Equal("<p></p>", resultString); Assert.Equal(documentPath, path); Assert.Equal(1337, version); }); var endpoint = new RazorDocumentSynchronizationEndpoint(Dispatcher, documentResolver, projectService.Object, LoggerFactory); var change = new TextDocumentContentChangeEvent() { Range = new Range(new Position(0, 3), new Position(0, 3)), RangeLength = 0, Text = "</p>" }; var request = new DidChangeTextDocumentParams() { ContentChanges = new Container <TextDocumentContentChangeEvent>(change), TextDocument = new VersionedTextDocumentIdentifier() { Uri = new Uri(documentPath), Version = 1337, } }; // Act await Task.Run(() => endpoint.Handle(request, default)); // Assert projectService.VerifyAll(); }
public void InsertAt_2_8() { TextDocumentContentChangeEvent change = new TextDocumentContentChangeEvent { Text = "abc", Range = new Range { Start = new Position { Line = 2, Character = 8 }, End = new Position { Line = 2, Character = 8 } }, RangeLength = 0 }; f.Apply(change); const string expected = "class A {\r\n var a:int;\r\n consabctructor(){}\r\n}"; Assert.AreEqual(expected, f.Sourcecode); }
public void AppendText() { TextDocumentContentChangeEvent change = new TextDocumentContentChangeEvent { Text = "//bla", Range = new Range { Start = new Position { Line = 3, Character = 1 }, End = new Position { Line = 3, Character = 1 } }, RangeLength = 0 }; f.Apply(change); const string expected = "class A {\r\n var a:int;\r\n constructor(){}\r\n}//bla"; Assert.AreEqual(expected, f.Sourcecode); }
public void Cut_All() { TextDocumentContentChangeEvent change = new TextDocumentContentChangeEvent { Text = "", Range = new Range { Start = new Position { Line = 0, Character = 0 }, End = new Position { Line = 3, Character = 1 } }, RangeLength = 49 }; f.Apply(change); const string expected = @""; Assert.AreEqual(expected, f.Sourcecode); }
public void Replace() { TextDocumentContentChangeEvent change = new TextDocumentContentChangeEvent { Text = "var b:string;", Range = new Range { Start = new Position { Line = 1, Character = 4 }, End = new Position { Line = 1, Character = 14 } }, RangeLength = 10 }; f.Apply(change); const string expected = "class A {\r\n var b:string;\r\n constructor(){}\r\n}"; Assert.AreEqual(expected, f.Sourcecode); }