internal MatchResult TryMatchOrRegionStart(Scanner s, out int idxMatchedItem) { if (null == this.m_communalRegex && !this.m_manualOnly) { this.BuildCommunalRegex(); } if (this.m_manualOnly) { return(this.TryManualMatch(s, out idxMatchedItem)); } Match m = this.m_communalRegex.Match(s.InputWindow.Window, s.Reader.PosCurrent - s.PosWindowStart); if (!m.Success) { // communal regex didn't match, manual matches are the only hope return(this.TryManualMatch(s, out idxMatchedItem)); } int idxCommunalMatchedItem = int.MaxValue; MatchResult communalResult; // we got a match in the communal regex. Now we need to find out which syntax item matched for (int i = 1; i < this.m_cntGroupsInCommunalRegex; i++) { if (m.Groups[i].Success && this.m_IdxItemFromGroupNumber[i] != -1) { idxCommunalMatchedItem = this.m_IdxItemFromGroupNumber[i]; break; } } ContainerItem matchedItem = (ContainerItem)this.m_items[idxCommunalMatchedItem]; matchedItem.BuildResult(s, out communalResult, m); // now, let's try the manual matches as well so we can then pick the best match MatchResult manualResult; int idxManualMatchedItem; manualResult = this.TryManualMatch(s, out idxManualMatchedItem); // Ok. At this point, we know that the communal regex matched. However, that does not necessarily mean that // the syntax item can start successfully. This happens because a region sometimes needs to satisfy other // conditions for it to start (eg, oneline regions). So, we now check if the communal match really was succesful. if (!communalResult.Success) { // ok, so the communal item fell through. Now we need to walk all of the items that come _later_ than the // communal item that fell through. This is because we know that no one _before_ it could have matched, // or else the communal regex would have picked that. for (idxCommunalMatchedItem++; idxManualMatchedItem < this.m_items.Length; idxManualMatchedItem++) { if (idxManualMatchedItem < idxCommunalMatchedItem) { break; } communalResult = ((ContainerItem)this.m_items[idxManualMatchedItem]).TryMatch(s); if (communalResult.Success) { break; } } if (!communalResult.Success) { idxCommunalMatchedItem = int.MaxValue; } } // at this point, we could have a successful communal item and/or a successful manual item, or both could have failed // if both failed, the caller will get a result with success set to false either way. If both worked, we return the // highest priority match if (idxManualMatchedItem < idxCommunalMatchedItem) { idxMatchedItem = idxManualMatchedItem; return(manualResult); } else { idxMatchedItem = idxCommunalMatchedItem; return(communalResult); } }
/// <summary> /// Builds a regex that captures the start patterns for all vim matches and region starts in this <see cref="SetOfSyntaxItems" />. /// Some patterns are not included and must be run manually (eg, the ones with <see cref="Pattern.LeadingContext">leading context</see>). /// </summary> private void BuildCommunalRegex() { if (!this.HasMatchesOrRegions) { string msg = StringExtensions.Fi("Invalid call to build regex for matches and regions on '{0}', which does " + "not have any matches or regions.", this); throw new InvalidOperationException(msg); } this.EnsureRunTime(); List <int> idxsManualMatches = new List <int>(2); bool canDoExplicitCapture = true; StringBuilder sb = new StringBuilder(30 * (this.Items.Length - this.IdxFirstNonKeyword)); sb.Append(@"\G("); bool addedItem = false; for (int idxItem = this.IdxFirstNonKeyword; idxItem < this.m_items.Length; idxItem++) { ContainerItem item = (ContainerItem)this.Items[idxItem]; string regex; bool isManualMatch; item.GetMatchRegex(out isManualMatch, out regex); if (isManualMatch) { idxsManualMatches.Add(idxItem); continue; } if (addedItem) { sb.Append("|"); } sb.AppendFormat("(?<i{0}>{1})", idxItem, regex); canDoExplicitCapture = canDoExplicitCapture && item.CanDoExplicitCapture; addedItem = true; } sb.Append(")"); if (0 < idxsManualMatches.Count) { this.m_idxsManualMatchItems = idxsManualMatches.ToArray(); } if (!addedItem) { this.m_manualOnly = true; return; } RegexOptions options = RegexOptions.Compiled; if (canDoExplicitCapture) { options |= RegexOptions.ExplicitCapture; } this.m_communalRegex = new Regex(sb.ToString(), options); this.m_cntGroupsInCommunalRegex = this.m_communalRegex.GetGroupNumbers().Length; this.m_IdxItemFromGroupNumber = new int[this.m_cntGroupsInCommunalRegex]; this.m_IdxItemFromGroupNumber[0] = -1; string[] groupNames = this.m_communalRegex.GetGroupNames(); for (int i = 1; i < groupNames.Length; i++) { string groupName = groupNames[i]; int idxMatchedItem; if (groupName[0] == 'i') { idxMatchedItem = int.Parse(groupName.Substring(1), CultureInfo.InvariantCulture); } else { idxMatchedItem = -1; } this.m_IdxItemFromGroupNumber[i] = idxMatchedItem; } }
private bool TryVimMatchesAndRegions(ref int posAdvanceTo) { int posMatchAt = this.Reader.PosCurrent; if (this.m_pendingMatch.PosMatchStart < posMatchAt) { this.m_pendingMatch.PosMatchStart = int.MaxValue; } if (this.m_posDontMatchUntil < posMatchAt && this.TopScope.ActiveSyntaxItems.HasMatchesOrRegions) { int idxMatchedItem; MatchResult result = this.TopScope.ActiveSyntaxItems.TryMatchOrRegionStart(this, out idxMatchedItem); for (int i = idxMatchedItem; i < this.TopScope.ActiveSyntaxItems.Items.Length; i++) { bool firstIteration = idxMatchedItem == i; if (!firstIteration) { result = ((ContainerItem)this.TopScope.ActiveSyntaxItems.Items[i]).TryMatch(this); } ContainerItem matchCandidate = result.ContainerItem; // the check below prevents infinite recursion when an item contains itself. It's still possible // to get infinite recursion using two items that contain each other. if ((matchCandidate == this.ItemInScope) && (posMatchAt == this.TopScope.PosMatchedAt)) { continue; } if (result.Success) { if (result.ContainerItem is VimMatch && result.PosAfterMatch == posMatchAt) { continue; } if (result.PosMatchStart < this.m_pendingMatch.PosMatchStart || (result.PosMatchStart == this.m_pendingMatch.PosMatchStart && this.m_pendingMatch.PositionInSyntaxFile < matchCandidate.PositionInSyntaxDefinition) ) { this.m_pendingMatch = result; this.m_pendingMatch.PositionInSyntaxFile = matchCandidate.PositionInSyntaxDefinition; // if the match starts at the match head, we'll call it the best possible match. In _theory_, we could find a match with // a negative start offset, but this would always force us to try every match, which would kill performance. Vim behaves like // we do. I imagine it's for the same reason. if (result.PosMatchStart <= posMatchAt) { break; } } } } } if (this.m_pendingMatch.PosMatchStart <= posMatchAt) { ContainerItem matchedItem = this.m_pendingMatch.ContainerItem; matchedItem.StartScopeForSuccesfulMatch(this, this.m_pendingMatch); this.ClearPendingMatch(); posAdvanceTo = this.TopScope.PosStart; return(true); } return(false); }