public GherkinFileEditorParserListener(ITextSnapshot textSnapshot, GherkinFileEditorClassifications classifications, GherkinFileEditorInfo previousGherkinFileEditorInfo, int changeLastLine, int changeLineDelta)
 {
     this.textSnapshot = textSnapshot;
     this.changeLineDelta = changeLineDelta;
     this.changeLastLine = changeLastLine;
     this.classifications = classifications;
     this.previousGherkinFileEditorInfo = previousGherkinFileEditorInfo;
     gherkinFileEditorInfo = new GherkinFileEditorInfo();
 }
        public GherkinFileEditorParserListener(ITextSnapshot textSnapshot, GherkinFileEditorClassifications classifications, GherkinFileEditorInfo previousGherkinFileEditorInfo, int lineOffset, int changeLastLine, int changeLineDelta)
        {
            this.textSnapshot = textSnapshot;
            this.changeLineDelta = changeLineDelta;
            this.changeLastLine = changeLastLine;
            this.lineOffset = lineOffset;
            this.classifications = classifications;
            this.previousGherkinFileEditorInfo = previousGherkinFileEditorInfo;
            gherkinFileEditorInfo = new GherkinFileEditorInfo();

            beforeFeature = !isPartialParsing;
        }
        private void TriggerChanges(GherkinFileEditorInfo gherkinFileEditorInfo, ChangeInfo changeInfo, ScenarioEditorInfo firstAffectedScenario = null, ScenarioEditorInfo firstUnchangedScenario = null)
        {
            this.GherkinFileEditorInfo = gherkinFileEditorInfo;

            var textSnapshot = changeInfo.TextSnapshot;
            int startPosition = 0;
            if (firstAffectedScenario != null)
                startPosition = textSnapshot.GetLineFromLineNumber(firstAffectedScenario.StartLine).Start;
            int endPosition = textSnapshot.Length;
            if (firstUnchangedScenario != null)
                endPosition = textSnapshot.GetLineFromLineNumber(firstUnchangedScenario.StartLine).Start;
            var snapshotSpan = new SnapshotSpan(textSnapshot, startPosition, endPosition - startPosition);

            if (ClassificationChanged != null)
            {
                ClassificationChanged(this, new ClassificationChangedEventArgs(snapshotSpan));
            }
            if (TagsChanged != null)
            {
                TagsChanged(this, new SnapshotSpanEventArgs(snapshotSpan));
            }
        }
        private void PartialParse(GherkinFileEditorInfo gherkinFileEditorInfo, ChangeInfo changeInfo, ScenarioEditorInfo firstAffectedScenario)
        {
            int parseStartPosition =
                changeInfo.TextSnapshot.GetLineFromLineNumber(firstAffectedScenario.StartLine).Start;

            string fileContent = changeInfo.TextSnapshot.GetText(parseStartPosition, changeInfo.TextSnapshot.Length - parseStartPosition);
            string fileHeader = changeInfo.TextSnapshot.GetText(0, parseStartPosition);
            I18n languageService = GetLanguageService(fileHeader);

            ScenarioEditorInfo firstUnchangedScenario;
            var partialResult = DoParsePartial(fileContent, languageService,
                                               firstAffectedScenario.StartLine,
                                               out firstUnchangedScenario,
                                               changeInfo.TextSnapshot,
                                               gherkinFileEditorInfo,
                                               changeInfo.ChangeLastLine,
                                               changeInfo.LineCountDelta);

            if (partialResult.HeaderClassificationSpans.Any())
            {
                //TODO: merge to the prev scenario?
                partialResult.HeaderClassificationSpans.Clear();
            }
            partialResult.HeaderClassificationSpans.AddRange(
                gherkinFileEditorInfo.HeaderClassificationSpans
                    .Select(cs => cs.Shift(changeInfo.TextSnapshot, 0)));

            // inserting the non-affected scenarios
            partialResult.ScenarioEditorInfos.InsertRange(0,
                                                          gherkinFileEditorInfo.ScenarioEditorInfos.TakeUntilItemExclusive(firstAffectedScenario)
                                                              .Select(senario => senario.Shift(changeInfo.TextSnapshot, 0, 0)));

            if (firstUnchangedScenario != null)
            {
                // inserting the non-effected scenarios at the end

                partialResult.ScenarioEditorInfos.AddRange(
                    gherkinFileEditorInfo.ScenarioEditorInfos.SkipFromItemInclusive(firstUnchangedScenario)
                        .Select(
                            scenario =>
                            scenario.Shift(changeInfo.TextSnapshot, changeInfo.LineCountDelta, changeInfo.PositionDelta)));
            }

            TriggerChanges(partialResult, changeInfo, firstAffectedScenario, firstUnchangedScenario);
        }
        private void ParseAndTriggerChanges(GherkinFileEditorInfo gherkinFileEditorInfo, ChangeInfo changeInfo)
        {
            if (gherkinFileEditorInfo == null)
            {
                // initial parsing
                FullParse(changeInfo);
                return;
            }

            // incremental parsing
            var firstAffectedScenario = gherkinFileEditorInfo.ScenarioEditorInfos.LastOrDefault(
                s => s.StartLine <= changeInfo.ChangeFirstLine);

            if (firstAffectedScenario == null)
            {
                FullParse(changeInfo);
                return;
            }

            PartialParse(gherkinFileEditorInfo, changeInfo, firstAffectedScenario);
        }
 private GherkinFileEditorInfo DoParsePartial(string fileContent, I18n languageService, int lineOffset, out ScenarioEditorInfo firstUnchangedScenario, ITextSnapshot textSnapshot, GherkinFileEditorInfo previousGherkinFileEditorInfo, int changeLastLine, int changeLineDelta)
 {
     var gherkinListener = new GherkinFileEditorParserListener(textSnapshot, classifications, previousGherkinFileEditorInfo, lineOffset, changeLastLine, changeLineDelta);
     return DoScan(fileContent, textSnapshot, lineOffset, languageService, gherkinListener, 0, out firstUnchangedScenario);
 }
        private GherkinFileEditorInfo DoParsePartial(string fileContent, I18n languageService, int lineOffset, out ScenarioEditorInfo firstUnchangedScenario, ITextSnapshot textSnapshot, GherkinFileEditorInfo previousGherkinFileEditorInfo, int changeLastLine, int changeLineDelta)
        {
            GherkinScanner scanner = new GherkinScanner(languageService, fileContent, lineOffset);

            var gherkinListener = new GherkinFileEditorParserListener(textSnapshot, classifications, previousGherkinFileEditorInfo, changeLastLine, changeLineDelta);
            firstUnchangedScenario = null;
            try
            {
                scanner.Scan(gherkinListener);
            }
            catch (PartialListeningDoneException partialListeningDoneException)
            {
                firstUnchangedScenario = partialListeningDoneException.FirstUnchangedScenario;
            }

            return gherkinListener.GetResult();
        }
        private void ParseAndTriggerChanges(GherkinFileEditorInfo gherkinFileEditorInfo, ChangeInfo changeInfo)
        {
            if (gherkinFileEditorInfo == null)
            {
                // initial parsing
                FullParse(changeInfo);
                return;
            }

            if (partialParseCount >= PartialParseCountLimit)
            {
                visualStudioTracer.Trace("Forced full parse after " + partialParseCount + " incremental parse", ParserTraceCategory);
                FullParse(changeInfo);
                return;
            }

            // incremental parsing
            var firstAffectedScenario = gherkinFileEditorInfo.ScenarioEditorInfos.LastOrDefault(
                s => s.StartLine <= changeInfo.ChangeFirstLine);

            if (firstAffectedScenario == null)
            {
                // We would not need to do a full parse when the header chenges, but it would
                // be too complicated to bring this case through the logic now.
                // So the side-effect is that we do a full parse when the header changes instead of an incremental.
                FullParse(changeInfo);
                return;
            }

            PartialParse(gherkinFileEditorInfo, changeInfo, firstAffectedScenario);
        }
        private void TriggerChanges(GherkinFileEditorInfo gherkinFileEditorInfo, ChangeInfo changeInfo, ScenarioEditorInfo firstAffectedScenario = null, ScenarioEditorInfo firstUnchangedScenario = null)
        {
            this.GherkinFileEditorInfo = gherkinFileEditorInfo;

            var textSnapshot = changeInfo.TextSnapshot;
            int startPosition = 0;
            if (firstAffectedScenario != null)
                startPosition = textSnapshot.GetLineFromLineNumber(firstAffectedScenario.StartLine).Start;
            int endPosition = textSnapshot.Length;
            if (firstUnchangedScenario != null)
                endPosition = textSnapshot.GetLineFromLineNumber(firstUnchangedScenario.StartLine).Start;

            // safety criteria to avoid argument execption in case of a wrong parser result
            if (startPosition >= endPosition)
                return;

            var snapshotSpan = new SnapshotSpan(textSnapshot, startPosition, endPosition - startPosition);

            if (ClassificationChanged != null)
            {
                ClassificationChanged(this, new ClassificationChangedEventArgs(snapshotSpan));
            }
            if (TagsChanged != null)
            {
                TagsChanged(this, new SnapshotSpanEventArgs(snapshotSpan));
            }
        }
        private void PartialParse(GherkinFileEditorInfo gherkinFileEditorInfo, ChangeInfo changeInfo, ScenarioEditorInfo firstAffectedScenario)
        {
            visualStudioTracer.Trace("Start incremental parsing", ParserTraceCategory);
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();

            partialParseCount++;

            int parseStartPosition = 
                changeInfo.TextSnapshot.GetLineFromLineNumber(firstAffectedScenario.StartLine).Start;

            string fileContent = changeInfo.TextSnapshot.GetText(parseStartPosition, changeInfo.TextSnapshot.Length - parseStartPosition);
            string fileHeader = changeInfo.TextSnapshot.GetText(0, parseStartPosition);
            I18n languageService = GetLanguageService(fileHeader);

            ScenarioEditorInfo firstUnchangedScenario;
            var partialResult = DoParsePartial(fileContent, languageService, 
                                               firstAffectedScenario.StartLine, 
                                               out firstUnchangedScenario, 
                                               changeInfo.TextSnapshot,
                                               gherkinFileEditorInfo,
                                               changeInfo.ChangeLastLine,
                                               changeInfo.LineCountDelta);

            if (partialResult.HeaderClassificationSpans.Any())
            {
                //TODO: merge to the prev scenario?
                partialResult.HeaderClassificationSpans.Clear();
            }
            partialResult.HeaderClassificationSpans.AddRange(
                gherkinFileEditorInfo.HeaderClassificationSpans
                    .Select(cs => cs.Shift(changeInfo.TextSnapshot, 0)));

            // inserting the non-affected scenarios
            partialResult.ScenarioEditorInfos.InsertRange(0,
                                                          gherkinFileEditorInfo.ScenarioEditorInfos.TakeUntilItemExclusive(firstAffectedScenario)
                                                              .Select(senario => senario.Shift(changeInfo.TextSnapshot, 0, 0)));

            ScenarioEditorInfo firstUnchangedScenarioShifted = null;
            if (firstUnchangedScenario != null)
            {
                // inserting the non-effected scenarios at the end

                int firstNewScenarioIndex = partialResult.ScenarioEditorInfos.Count;
                partialResult.ScenarioEditorInfos.AddRange(
                    gherkinFileEditorInfo.ScenarioEditorInfos.SkipFromItemInclusive(firstUnchangedScenario)
                        .Select(
                            scenario =>
                            scenario.Shift(changeInfo.TextSnapshot, changeInfo.LineCountDelta, changeInfo.PositionDelta)));

                firstUnchangedScenarioShifted = partialResult.ScenarioEditorInfos.Count > firstNewScenarioIndex
                                             ? partialResult.ScenarioEditorInfos[firstNewScenarioIndex]
                                             : null;
            }

            TriggerChanges(partialResult, changeInfo, firstAffectedScenario, firstUnchangedScenarioShifted);

            stopwatch.Stop();
            TraceFinishParse(stopwatch, "incremental");
        }