public DocumentChange( IDocumentSnapshot documentSnapshot, DocumentChangeType changeType, int newIndex, int oldIndex) { DocumentSnapshot = documentSnapshot; ChangeType = changeType; NewIndex = newIndex; OldIndex = oldIndex; }
/// <summary> /// Illustration : lines with code elements continued from previous line or with a continuation on next line (before update) /// SW represents a code element starting word /// [ SW x] /// [ x] /// [SW x] /// [ SW ] /// [ x SW] /// A DocumentChange intersects with a previously parsed multiline code element if : /// * LineInserted : /// - all previous lines until the last starting word /// (get previous lines until the previous code element is found) /// - all the next lines until the next starting word /// (get next lines until the next code element is found, /// do not reset the last line if the next code element starts at the beginning of the line) /// * LineUpdated / LineRemoved : /// - all previous lines until the last starting word /// (get previous lines until the previous code element is found) /// - all the next lines until the next starting word /// (get next lines until the next code element is found, /// do not reset the last line if the next code element starts at the beginning of the line) /// When navigating to previous or next line searching for a code element, we can stop when a fresh insertion / update is encountered. /// When we reset a line which was not directly updated, and where the code element started in the middle of the line, /// we must "remember" at which token we must start parsing. /// In conclusion, the incremental parsing step for code elements is divided in two steps : /// 1. List all the starting and stop tokens of sections to parse with the rules above /// 2. Parse code elements beginning with starting token and until we reach stop token /// </summary> private static ParseSection CheckIfAdjacentLinesNeedRefresh(DocumentChangeType changeType, int lineIndex, ISearchableReadOnlyList <CodeElementsLine> documentLines, PrepareDocumentLineForUpdate prepareDocumentLineForUpdate, IList <DocumentChange <ICodeElementsLine> > codeElementsLinesChanges, ParseSection lastParseSection) { ParseSection currentParseSection = new ParseSection(); // Navigate backwards to the start of the multiline code element if (lineIndex > 0) { int previousLineIndex = lineIndex; int lastLineIndexReset = lastParseSection != null ? lastParseSection.StopLineIndex : -1; IEnumerator <CodeElementsLine> reversedEnumerator = documentLines.GetEnumerator(previousLineIndex - 1, -1, true); bool previousLineHasCodeElements = false; while (reversedEnumerator.MoveNext() && (--previousLineIndex > lastLineIndexReset)) { // Get the previous line until the first code element is encountered CodeElementsLine previousLine = reversedEnumerator.Current; // The start of the parse section is delimited by the previous CodeElement if (previousLine != null) { previousLineHasCodeElements = previousLine.HasCodeElements; if (previousLineHasCodeElements) { currentParseSection.StartLineIndex = previousLineIndex; currentParseSection.StartToken = previousLine.CodeElements.First().ConsumedTokens.FirstOrDefault(); } // All lines contained in the parse section could be modified, and should be reset previousLine = (CodeElementsLine)prepareDocumentLineForUpdate(previousLineIndex, previousLine, CompilationStep.CodeElementsParser); previousLine.ResetCodeElements(); codeElementsLinesChanges.Add(new DocumentChange <ICodeElementsLine>(DocumentChangeType.LineUpdated, previousLineIndex, previousLine)); } // Stop iterating backwards as soon as the start of an old CodeElement is found if (previousLineHasCodeElements) { break; } } // If no CodeElement was found on the previous lines, current parse section starts at the beggining of the file // (because last parseSection could only stop at a CodeElement or at the end of the file) if (!previousLineHasCodeElements) { currentParseSection.StartLineIndex = 0; currentParseSection.StartToken = null; } } // If line 0 was updated, current parse section starts at the beggining of the file else { currentParseSection.StartLineIndex = 0; currentParseSection.StartToken = null; } // Navigate forwards to the end of the multiline code element if (lineIndex < (documentLines.Count - 1)) { int nextLineIndex = lineIndex; IEnumerator <CodeElementsLine> enumerator = documentLines.GetEnumerator(nextLineIndex + 1, -1, false); bool nextLineHasCodeElements = false; while (enumerator.MoveNext()) { // Get the next line until non continuation line is encountered nextLineIndex++; CodeElementsLine nextLine = enumerator.Current; // Check if the next CodeElement found starts at the beginning of the line if (nextLine != null) { nextLineHasCodeElements = nextLine.HasCodeElements; bool nextCodeElementStartsAtTheBeginningOfTheLine = false; if (nextLineHasCodeElements) { try { Token startTokenForNextParseSection = nextLine.CodeElements.First().ConsumedTokens.FirstOrDefault(); Token firstSourceTokenOfThisLine = nextLine.TokensWithCompilerDirectives.First(token => token.Channel == Token.CHANNEL_SourceTokens); nextCodeElementStartsAtTheBeginningOfTheLine = startTokenForNextParseSection == firstSourceTokenOfThisLine; } catch (System.InvalidOperationException /*e*/) {//JCM: 28/08/2017: I noticed that this Exception can occur if: it doesn't exists a token which verifies the predicate: token.Channel == Token.CHANNEL_SourceToken nextCodeElementStartsAtTheBeginningOfTheLine = false; } } // All lines contained in the parse section could be modified if (!nextCodeElementStartsAtTheBeginningOfTheLine) { // TO DO : ERROR below, will not work if we have source tokens from previous CodeElement + one other CodeElement on the same line // => the other CodeElement will be deleted by prepareDocumentLineForUpdate and not parsed again nextLine = (CodeElementsLine)prepareDocumentLineForUpdate(nextLineIndex, nextLine, CompilationStep.CodeElementsParser); codeElementsLinesChanges.Add(new DocumentChange <ICodeElementsLine>(DocumentChangeType.LineUpdated, nextLineIndex, nextLine)); } // Stop iterating forwards as soon as the start of an old CodeElement is found if (nextLineHasCodeElements) { currentParseSection.StopLineIndex = nextLineIndex; currentParseSection.StopToken = nextLine.CodeElements.First().ConsumedTokens.FirstOrDefault(); currentParseSection.StopTokenIsFirstTokenOfTheLine = nextCodeElementStartsAtTheBeginningOfTheLine; break; } } } // If no CodeElement was found on the next lines, current parse section current parse section ends at the end of the file if (!nextLineHasCodeElements) { currentParseSection.StopLineIndex = nextLineIndex; currentParseSection.StopToken = null; currentParseSection.StopTokenIsFirstTokenOfTheLine = false; } } // If last line was updated, or if no CodeElement was found after the updated line, current parse section ends at the end of the file else { currentParseSection.StopLineIndex = documentLines.Count - 1; currentParseSection.StopToken = null; currentParseSection.StopTokenIsFirstTokenOfTheLine = false; } return(currentParseSection); }
/// <summary> /// Constructor /// </summary> /// <param name="type">Type of change applied to the line</param> /// <param name="lineIndex">Index of the line which was changed</param> /// <param name="newLine">New line content after the update (null in case of a LineRemoved event)</param> public DocumentChange(DocumentChangeType type, int lineIndex, T newLine) { Type = type; LineIndex = lineIndex; NewLine = newLine; }
/// <summary> /// Illustration : lines with directives continued from previous line or with a continuation on next line (before update) are marked with a cross /// [ ] /// [ x] /// [x x] /// [x ] /// [ ] /// A DocumentChange intersects with a previously parsed multiline compiler directive if : /// * LineInserted : /// - the next line was a continuation /// * LineUpdated / LineRemoved : /// - the previous line was continued /// - the next line was a continuation /// When navigating to previous or next line searching for a continuation, we must ignore all fresh insertions / updates. /// We must then reset all processed tokens lines involved in a multiline compiler directive. /// </summary> private static int CheckIfAdjacentLinesNeedRefresh(DocumentChangeType changeType, int lineIndex, ISearchableReadOnlyList <ProcessedTokensLine> documentLines, PrepareDocumentLineForUpdate prepareDocumentLineForUpdate, IList <DocumentChange <IProcessedTokensLine> > processedTokensLinesChanges, int lastLineIndexReset) { // Navigate backwards to the start of the multiline compiler directive if (lineIndex > 0) { int previousLineIndex = lineIndex; IEnumerator <ProcessedTokensLine> reversedEnumerator = documentLines.GetEnumerator(previousLineIndex - 1, -1, true); while (reversedEnumerator.MoveNext() && (--previousLineIndex > lastLineIndexReset)) { // Get the previous line until a non continued line is encountered ProcessedTokensLine previousLine = reversedEnumerator.Current; // A reset line was already treated by the previous call to CheckIfAdjacentLinesNeedRefresh : stop searching if (previousLine.PreprocessingState == ProcessedTokensLine.PreprocessorState.NeedsCompilerDirectiveParsing) { break; } // Previous line is a continuation : reset this line and continue navigating backwards // Previous line is not a continuation but is continued : reset this line and stop navigating backwards else if (previousLine.HasDirectiveTokenContinuationFromPreviousLine || previousLine.HasDirectiveTokenContinuedOnNextLine) { lineIndex = previousLineIndex; previousLine = (ProcessedTokensLine)prepareDocumentLineForUpdate(previousLineIndex, previousLine, CompilationStep.Preprocessor); processedTokensLinesChanges.Add(new DocumentChange <IProcessedTokensLine>(DocumentChangeType.LineUpdated, previousLineIndex, previousLine)); if (!previousLine.HasDirectiveTokenContinuationFromPreviousLine) { break; } } // Previous line not involved in a multiline compiler directive : stop searching else { break; } } } // Navigate forwards to the end of the multiline compiler directive if (lineIndex < (documentLines.Count - 1)) { int nextLineIndex = lineIndex; IEnumerator <ProcessedTokensLine> enumerator = documentLines.GetEnumerator(nextLineIndex + 1, -1, true); while (enumerator.MoveNext()) { // Get the next line until non continuation line is encountered nextLineIndex++; ProcessedTokensLine nextLine = enumerator.Current; // A reset line will be treated by the next call to CheckIfAdjacentLinesNeedRefresh : stop searching if (nextLine.PreprocessingState == ProcessedTokensLine.PreprocessorState.NeedsCompilerDirectiveParsing) { break; } // Next line is a continuation and is continued: reset this line and continue navigating forwards // Next line is a continuation but is not continued : reset this line and stop navigating forwards else if (nextLine.HasDirectiveTokenContinuationFromPreviousLine) { nextLine = (ProcessedTokensLine)prepareDocumentLineForUpdate(nextLineIndex, nextLine, CompilationStep.Preprocessor); processedTokensLinesChanges.Add(new DocumentChange <IProcessedTokensLine>(DocumentChangeType.LineUpdated, nextLineIndex, nextLine)); lastLineIndexReset = nextLineIndex; if (!nextLine.HasDirectiveTokenContinuedOnNextLine) { break; } } // Previous line not involved in a multiline compiler directive : stop searching else { break; } } } return(lastLineIndexReset > lineIndex ? lastLineIndexReset : lineIndex); }
/// <summary> /// Illustration : lines with directives continued from previous line or with a continuation on next line (before update) are marked with a cross /// [ ] /// [ x] /// [x x] /// [x ] /// [ ] /// A DocumentChange intersects with a previously parsed multiline compiler directive if : /// * LineInserted : /// - the next line was a continuation /// * LineUpdated / LineRemoved : /// - the previous line was continued /// - the next line was a continuation /// When navigating to previous or next line searching for a continuation, we must ignore all fresh insertions / updates. /// We must then reset all processed tokens lines involved in a multiline compiler directive. /// </summary> private static int CheckIfAdjacentLinesNeedRefresh(DocumentChangeType changeType, int lineIndex, ISearchableReadOnlyList<ProcessedTokensLine> documentLines, PrepareDocumentLineForUpdate prepareDocumentLineForUpdate, IList<DocumentChange<IProcessedTokensLine>> processedTokensLinesChanges, int lastLineIndexReset) { // Navigate backwards to the start of the multiline compiler directive if (lineIndex > 0) { int previousLineIndex = lineIndex; IEnumerator<ProcessedTokensLine> reversedEnumerator = documentLines.GetEnumerator(previousLineIndex - 1, -1, true); while (reversedEnumerator.MoveNext() && (--previousLineIndex > lastLineIndexReset)) { // Get the previous line until a non continued line is encountered ProcessedTokensLine previousLine = reversedEnumerator.Current; // A reset line was already treated by the previous call to CheckIfAdjacentLinesNeedRefresh : stop searching if (previousLine.PreprocessingState == ProcessedTokensLine.PreprocessorState.NeedsCompilerDirectiveParsing) { break; } // Previous line is a continuation : reset this line and continue navigating backwards // Previous line is not a continuation but is continued : reset this line and stop navigating backwards else if (previousLine.HasDirectiveTokenContinuationFromPreviousLine || previousLine.HasDirectiveTokenContinuedOnNextLine) { lineIndex = previousLineIndex; previousLine = (ProcessedTokensLine)prepareDocumentLineForUpdate(previousLineIndex, previousLine, CompilationStep.Preprocessor); processedTokensLinesChanges.Add(new DocumentChange<IProcessedTokensLine>(DocumentChangeType.LineUpdated, previousLineIndex, previousLine)); if(!previousLine.HasDirectiveTokenContinuationFromPreviousLine) { break; } } // Previous line not involved in a multiline compiler directive : stop searching else { break; } } } // Navigate forwards to the end of the multiline compiler directive if (lineIndex < (documentLines.Count - 1)) { int nextLineIndex = lineIndex; IEnumerator<ProcessedTokensLine> enumerator = documentLines.GetEnumerator(nextLineIndex + 1, -1, true); while (enumerator.MoveNext()) { // Get the next line until non continuation line is encountered nextLineIndex++; ProcessedTokensLine nextLine = enumerator.Current; // A reset line will be treated by the next call to CheckIfAdjacentLinesNeedRefresh : stop searching if (nextLine.PreprocessingState == ProcessedTokensLine.PreprocessorState.NeedsCompilerDirectiveParsing) { break; } // Next line is a continuation and is continued: reset this line and continue navigating forwards // Next line is a continuation but is not continued : reset this line and stop navigating forwards else if (nextLine.HasDirectiveTokenContinuationFromPreviousLine) { nextLine = (ProcessedTokensLine)prepareDocumentLineForUpdate(nextLineIndex, nextLine, CompilationStep.Preprocessor); processedTokensLinesChanges.Add(new DocumentChange<IProcessedTokensLine>(DocumentChangeType.LineUpdated, nextLineIndex, nextLine)); lastLineIndexReset = nextLineIndex; if (!nextLine.HasDirectiveTokenContinuedOnNextLine) { break; } } // Previous line not involved in a multiline compiler directive : stop searching else { break; } } } return lastLineIndexReset > lineIndex ? lastLineIndexReset : lineIndex; }
/// <summary> /// Illustration : lines with code elements continued from previous line or with a continuation on next line (before update) /// SW represents a code element starting word /// [ SW x] /// [ x] /// [SW x] /// [ SW ] /// [ x SW] /// A DocumentChange intersects with a previously parsed multiline code element if : /// * LineInserted : /// - all previous lines until the last starting word /// (get previous lines until the previous code element is found) /// - all the next lines until the next starting word /// (get next lines until the next code element is found, /// do not reset the last line if the next code element starts at the beginning of the line) /// * LineUpdated / LineRemoved : /// - all previous lines until the last starting word /// (get previous lines until the previous code element is found) /// - all the next lines until the next starting word /// (get next lines until the next code element is found, /// do not reset the last line if the next code element starts at the beginning of the line) /// When navigating to previous or next line searching for a code element, we can stop when a fresh insertion / update is encountered. /// When we reset a line which was not directly updated, and where the code element started in the middle of the line, /// we must "remember" at which token we must start parsing. /// In conclusion, the incremental parsing step for code elements is divided in two steps : /// 1. List all the starting and stop tokens of sections to parse with the rules above /// 2. Parse code elements beginning with starting token and until we reach stop token /// </summary> private static ParseSection CheckIfAdjacentLinesNeedRefresh(DocumentChangeType changeType, int lineIndex, ISearchableReadOnlyList<CodeElementsLine> documentLines, PrepareDocumentLineForUpdate prepareDocumentLineForUpdate, IList<DocumentChange<ICodeElementsLine>> codeElementsLinesChanges, ParseSection lastParseSection) { ParseSection currentParseSection = new ParseSection(); // Navigate backwards to the start of the multiline code element if (lineIndex > 0) { int previousLineIndex = lineIndex; int lastLineIndexReset = lastParseSection != null ? lastParseSection.StopLineIndex : - 1; IEnumerator<CodeElementsLine> reversedEnumerator = documentLines.GetEnumerator(previousLineIndex - 1, -1, true); while (reversedEnumerator.MoveNext() && (--previousLineIndex > lastLineIndexReset)) { // Get the previous line until the first code element is encountered CodeElementsLine previousLine = reversedEnumerator.Current; // The start of the parse section is delimited by the previous CodeElement bool previousLineHasCodeElements = previousLine.HasCodeElements; if (previousLineHasCodeElements) { currentParseSection.StartLineIndex = previousLineIndex; currentParseSection.StartToken = previousLine.CodeElements[0].ConsumedTokens[0]; } // All lines contained in the parse section could be modified, and should be reset previousLine = (CodeElementsLine)prepareDocumentLineForUpdate(previousLineIndex, previousLine, CompilationStep.CodeElementsParser); previousLine.ResetCodeElements(); codeElementsLinesChanges.Add(new DocumentChange<ICodeElementsLine>(DocumentChangeType.LineUpdated, previousLineIndex, previousLine)); // Stop iterating backwards as soon as the start of an old CodeElement is found if (previousLineHasCodeElements) { break; } } } // Navigate forwards to the end of the multiline code element if (lineIndex < (documentLines.Count - 1)) { int nextLineIndex = lineIndex; IEnumerator<CodeElementsLine> enumerator = documentLines.GetEnumerator(nextLineIndex + 1, -1, true); while (enumerator.MoveNext()) { // Get the next line until non continuation line is encountered nextLineIndex++; CodeElementsLine nextLine = enumerator.Current; // Check if the next CodeElement found starts at the beginning of the line bool nextCodeElementStartsAtTheBeginningOfTheLine = false; if (nextLine.HasCodeElements) { Token startTokenForNextParseSection = nextLine.CodeElements[0].ConsumedTokens[0]; Token firstSourceTokenOfThisLine = nextLine.TokensWithCompilerDirectives.First(token => token.Channel == Token.CHANNEL_SourceTokens); nextCodeElementStartsAtTheBeginningOfTheLine = startTokenForNextParseSection == firstSourceTokenOfThisLine; } // All lines contained in the parse section could be modified if (!nextCodeElementStartsAtTheBeginningOfTheLine) { nextLine = (CodeElementsLine)prepareDocumentLineForUpdate(nextLineIndex, nextLine, CompilationStep.CodeElementsParser); codeElementsLinesChanges.Add(new DocumentChange<ICodeElementsLine>(DocumentChangeType.LineUpdated, nextLineIndex, nextLine)); } // Stop iterating forwards as soon as the start of an old CodeElement is found if (nextLine.HasCodeElements) { if (!nextCodeElementStartsAtTheBeginningOfTheLine) { currentParseSection.StopLineIndex = nextLineIndex; currentParseSection.StopToken = nextLine.CodeElements[0].ConsumedTokens[0]; } else { currentParseSection.StopLineIndex = nextLineIndex; while (currentParseSection.StopToken == null && currentParseSection.StopLineIndex >= 1) { currentParseSection.StopLineIndex = currentParseSection.StopLineIndex - 1; currentParseSection.StopToken = documentLines[currentParseSection.StopLineIndex].TokensWithCompilerDirectives.Last(token => token.Channel == Token.CHANNEL_SourceTokens); } } currentParseSection.StopTokenIsLastTokenOfTheLine = nextCodeElementStartsAtTheBeginningOfTheLine; break; } } } // Current parse section ends with the updated line else { currentParseSection.StopLineIndex = lineIndex + 1; while (currentParseSection.StopToken == null && currentParseSection.StopLineIndex >= 1) { currentParseSection.StopLineIndex = currentParseSection.StopLineIndex - 1; currentParseSection.StopToken = documentLines[currentParseSection.StopLineIndex].TokensWithCompilerDirectives.Last(token => token.Channel == Token.CHANNEL_SourceTokens); } currentParseSection.StopTokenIsLastTokenOfTheLine = true; } return currentParseSection; }
/// <summary> /// Initializes a new instance of the <see cref="DocumentChangedEventArgs"/> class. /// </summary> /// <param name="change"> /// The change. /// </param> public DocumentChangedEventArgs(DocumentChangeType change) { this.Change = change; }