void Process(string file, bool printFileName, Dictionary<ColoredMessage, CaptureResult> ctxGroups = null) { var output = new ColoredMessage(); var body = ReadAllText(file, out var encoding); var matchLines = new SortedDictionary<int, Line>(); var replaced = body; foreach (var regex in _regexes) { var matches = regex.Matches(body); // var currentlnStart = -2; // initially unknown foreach (Match match in matches) { if (match.Length == 0) { continue; // ignore zero-length matches and prevent index out of range when index >= length (zero length match at the end of a string) } // find the start of the line var lineStart = body.LastIndexOf('\n', match.Index); lineStart++; if (!matchLines.TryGetValue(lineStart, out var line)) { matchLines[lineStart] = line = new Line { Start = lineStart, }; } line.Matches.Add(match.Index, new LineMatch(regex, match)); } if (ReplaceTo != null) { replaced = regex.Replace(replaced, ReplaceTo); // var rr = regex.ReplaceBreakout(replaced, ReplaceTo); } } if (ctxGroups == null && printFileName && matchLines.Any()) { PrintFileName(file); } if (!OutputControlOptions.FilesWithMatches) { // fill context lines if (ContextCaptureOptions.ContextAround != 0 || ContextCaptureOptions.Before != 0 || ContextCaptureOptions.After != 0) { foreach (var line in matchLines.Values.ToArray()) { var prev = line.Start; var before = Math.Max(ContextCaptureOptions.Before, ContextCaptureOptions.ContextAround); for (; before > 0; before--) { if (prev >= 2) { prev = body.LastIndexOf('\n', prev - 2); prev++; if (!matchLines.ContainsKey(prev)) { matchLines[prev] = new Line { Start = prev, }; } } } prev = line.Start; var after = Math.Max(ContextCaptureOptions.After, ContextCaptureOptions.ContextAround); for (; after > 0; after--) { prev = body.IndexOf('\n', prev); if (prev > 0) { prev++; if (!matchLines.ContainsKey(prev)) { matchLines[prev] = new Line { Start = prev, }; } } } } } foreach (var line in matchLines.Values) { var eol = body.IndexOf('\n', line.Start); if (eol == -1) { eol = body.Length - 1; // jump to last char } else { eol--; // jump to char before \n } // Windows CR LF file if (body[eol] == '\r') { eol--; // jump one more time } // var str = body.Substring(line.Key, eol); void PrintLine(bool replaceMode = false) { var printPosition = line.Start; foreach (var lineMatch in line.Matches.Values) { var match = lineMatch.Match; if (match.Index > printPosition) { output.Write(ConsoleColor.Gray, body.Substring(printPosition, match.Index - printPosition)); } if (!replaceMode) { output.Write(ConsoleColor.Red, match.Value); } else { output.Write(ConsoleColor.Green, GetReplacementString(lineMatch.Regex, match, body, ReplaceTo)); } printPosition = match.Index + match.Length; } var len = eol + 1 - printPosition; if (len >= 0 && printPosition < body.Length && (len - printPosition) < body.Length) { output.Write(ConsoleColor.Gray, body.Substring(printPosition, len) + Environment.NewLine); } else { output.Write(null, Environment.NewLine); } } PrintLine(); if (ReplaceTo != null) { PrintLine(true); } } } if (Save && matchLines.Any() && body != replaced) { // open existing file for replace! using var fileStream = new FileStream(file, FileMode.Open, FileAccess.Write, FileShare.Read, FileBufferSize, FileOptions.SequentialScan); using var sw = new StreamWriter(fileStream, encoding); sw.Write(replaced); sw.Flush(); fileStream.SetLength(fileStream.Position); // truncate! } if (ctxGroups != null) { // context grouping... will print later if (output.Parts.Count > 0) { if (!ctxGroups.TryGetValue(output, out var capture)) { ctxGroups[output] = capture = new CaptureResult(); } capture.FileNames.Add(file); } } else { // no context grouping, print directly now if (!OutputControlOptions.FilesWithMatches) { output.ToConsole(); } } }