diff --git a/src/CommandLine/CaptureSlim.cs b/src/CommandLine/CaptureSlim.cs new file mode 100644 index 00000000..8a8adbc4 --- /dev/null +++ b/src/CommandLine/CaptureSlim.cs @@ -0,0 +1,5 @@ +// Copyright (c) Josef Pihrt. All rights reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Orang.CommandLine; + +internal readonly record struct CaptureSlim(string Value, int Index, int Length); diff --git a/src/CommandLine/CommandLine.csproj b/src/CommandLine/CommandLine.csproj index b6ed5512..42aed721 100644 --- a/src/CommandLine/CommandLine.csproj +++ b/src/CommandLine/CommandLine.csproj @@ -31,12 +31,13 @@ - + + diff --git a/src/CommandLine/Commands/CommonReplaceCommand`1.cs b/src/CommandLine/Commands/CommonReplaceCommand`1.cs index 1bf7461f..3e9a1db6 100644 --- a/src/CommandLine/Commands/CommonReplaceCommand`1.cs +++ b/src/CommandLine/Commands/CommonReplaceCommand`1.cs @@ -71,7 +71,7 @@ private void ExecuteInput(SearchContext context, string input) predicate: contentFilter.Predicate, captures: groups); - IEnumerable captures = GetCaptures(groups, context.CancellationToken) + IEnumerable captures = GetCaptures(groups, null, context.CancellationToken) ?? groups.Select(f => (ICapture)new RegexCapture(f)); using (IEnumerator en = captures.GetEnumerator()) @@ -151,7 +151,7 @@ protected override void ExecuteMatchWithContentCore( predicate: Options.ContentFilter!.Predicate, captures: groups); - List? captures = GetCaptures(groups, context.CancellationToken); + List? captures = GetCaptures(groups, fileMatch, context.CancellationToken); using (IEnumerator en = (captures ?? groups.Select(f => (ICapture)new RegexCapture(f))).GetEnumerator()) { @@ -447,6 +447,7 @@ private void WriteMatches(ContentWriter writer, IEnumerator en, Search protected virtual List? GetCaptures( List groups, + FileMatch? fileMatch, CancellationToken cancellationToken) { return null; diff --git a/src/CommandLine/Commands/SpellcheckCommand.cs b/src/CommandLine/Commands/SpellcheckCommand.cs index 567cd40c..68b5995f 100644 --- a/src/CommandLine/Commands/SpellcheckCommand.cs +++ b/src/CommandLine/Commands/SpellcheckCommand.cs @@ -9,6 +9,7 @@ using System.Text.RegularExpressions; using System.Threading; using Microsoft.CodeAnalysis.Text; +using Orang.FileSystem; using Orang.Spelling; namespace Orang.CommandLine; @@ -36,33 +37,51 @@ protected override void ExecuteCore(SearchContext context) return; } - protected override List? GetCaptures(List groups, CancellationToken cancellationToken) + protected override List? GetCaptures( + List groups, + FileMatch? fileMatch, + CancellationToken cancellationToken) { var captures = new List(); List? filteredSpans = null; foreach (Capture capture in groups) { - foreach (SpellingMatch spellingMatch in SpellcheckState.Spellchecker.AnalyzeText(capture.Value)) + IEnumerable? subcaptures = null; + + if (fileMatch is not null + && FileSystemHelpers.HasExtension(fileMatch.Path, "md")) + { + subcaptures = MarkdownProcessor.ProcessText(capture.Value); + } + else { - var captureInfo = new SpellingCapture( - spellingMatch.Value, - capture.Index + spellingMatch.Index, - containingValue: spellingMatch.Parent, - containingValueIndex: spellingMatch.ParentIndex); + subcaptures = new[] { new CaptureSlim(capture.Value, capture.Index, capture.Length) }; + } - if (filteredSpans is null) - filteredSpans = GetFilteredSpans(groups, cancellationToken); + foreach (CaptureSlim subcapture in subcaptures) + { + foreach (SpellingMatch spellingMatch in SpellcheckState.Spellchecker.AnalyzeText(subcapture.Value)) + { + var captureInfo = new SpellingCapture( + spellingMatch.Value, + capture.Index + subcapture.Index + spellingMatch.Index, + containingValue: spellingMatch.Parent, + containingValueIndex: spellingMatch.ParentIndex); - var captureSpan = new TextSpan(captureInfo.Index, captureInfo.Length); + if (filteredSpans is null) + filteredSpans = GetFilteredSpans(groups, cancellationToken); - foreach (TextSpan filteredSpan in filteredSpans) - { - if (filteredSpan.IntersectsWith(captureSpan)) - continue; - } + var captureSpan = new TextSpan(captureInfo.Index, captureInfo.Length); + + foreach (TextSpan filteredSpan in filteredSpans) + { + if (filteredSpan.IntersectsWith(captureSpan)) + continue; + } - captures.Add(captureInfo); + captures.Add(captureInfo); + } } } diff --git a/src/CommandLine/Markdown/MarkdownProcessor.cs b/src/CommandLine/Markdown/MarkdownProcessor.cs new file mode 100644 index 00000000..7aceaaad --- /dev/null +++ b/src/CommandLine/Markdown/MarkdownProcessor.cs @@ -0,0 +1,103 @@ +// Copyright (c) Josef Pihrt. All rights reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Diagnostics; +using Markdig; +using Markdig.Extensions.CustomContainers; +using Markdig.Extensions.Tables; +using Markdig.Helpers; +using Markdig.Syntax; +using Markdig.Syntax.Inlines; + +namespace Orang.CommandLine; + +internal static class MarkdownProcessor +{ + private static readonly MarkdownPipeline _pipeline = new MarkdownPipelineBuilder().UseAdvancedExtensions().Build(); + + public static IEnumerable ProcessText(string text) + { + MarkdownDocument document = Markdown.Parse(text, _pipeline); + + foreach (MarkdownObject item in document.Descendants()) + { + switch (item) + { + case CodeInline code: + { + yield return new CaptureSlim(code.Content, code.Span.Start + code.DelimiterCount, code.Span.Length); + break; + } + case LiteralInline literal: + { + string value = literal.Content.ToString(); + SourceSpan span = literal.Span; + int offset = (literal.IsFirstCharacterEscaped) ? 1 : 0; + + yield return new CaptureSlim(value, span.Start + offset, span.Length + offset); + break; + } + case LinkInline link: + { + string? label = link.Label; + string? title = link.Title; + + if (!string.IsNullOrEmpty(label)) + yield return new CaptureSlim(label, link.LabelSpan.Start, link.LabelSpan.Length); + + if (!string.IsNullOrEmpty(title)) + yield return new CaptureSlim(title, link.TitleSpan.Start, link.TitleSpan.Length); + + break; + } + case LinkReferenceDefinition linkReferenceDef: + { + string? label = linkReferenceDef.Label; + string? title = linkReferenceDef.Title; + + if (!string.IsNullOrEmpty(label)) + yield return new CaptureSlim(label, linkReferenceDef.LabelSpan.Start, linkReferenceDef.LabelSpan.Length); + + if (!string.IsNullOrEmpty(title)) + yield return new CaptureSlim(title, linkReferenceDef.TitleSpan.Start, linkReferenceDef.TitleSpan.Length); + + break; + } + case CodeBlock codeBlock: + { + foreach (StringLine line in codeBlock.Lines.Lines) + { + StringSlice slice = line.Slice; + yield return new CaptureSlim(slice.ToString(), slice.Start, slice.Length); + } + + break; + } + case ContainerInline: // EmphasisInline, DelimiterInline, EmphasisDelimiterInline, LinkDelimiterInline + case AutolinkInline: + case HtmlEntityInline: + case LineBreakInline: + case HtmlInline: + case HeadingBlock: + case ListBlock: + case ListItemBlock: + case ParagraphBlock: + case ThematicBreakBlock: + case LinkReferenceDefinitionGroup: + case Table: + case TableRow: + case TableCell: + case QuoteBlock: + case CustomContainer: + { + break; + } + default: + { + Debug.Fail(item.GetType().FullName); + break; + } + } + } + } +} diff --git a/src/CommandLine/OptionValues.cs b/src/CommandLine/OptionValues.cs index 3ca25472..a936eece 100644 --- a/src/CommandLine/OptionValues.cs +++ b/src/CommandLine/OptionValues.cs @@ -114,9 +114,9 @@ internal static class OptionValues public static readonly KeyValuePairOptionValue Encoding = KeyValuePairOptionValue.Create("encoding", MetaValues.Encoding, shortKey: "e"); - public static readonly KeyValuePairOptionValue FileProperty_CreationTime = KeyValuePairOptionValue.Create("creation-time", "", shortKey: "ct", helpValue: "c[reation-]t[ime][=]", description: "Show file's creation time and optionally define condition (See 'Expression syntax' for other expressions).", canContainExpression: true); - public static readonly KeyValuePairOptionValue FileProperty_ModifiedTime = KeyValuePairOptionValue.Create("modified-time", "", shortKey: "mt", helpValue: "m[odified-]t[ime][=]", description: "Show file's modified time and optionally define condition (See 'Expression syntax' for other expressions).", canContainExpression: true); - public static readonly KeyValuePairOptionValue FileProperty_Size = KeyValuePairOptionValue.Create("size", "", helpValue: "s[ize][=]", description: "Show file's size and optionally define condition (See 'Expression syntax' for other expressions).", canContainExpression: true); + public static readonly KeyValuePairOptionValue FileProperty_CreationTime = KeyValuePairOptionValue.Create("creation-time", "", shortKey: "ct", helpValue: "c[reation-]t[ime]=", description: "Show file's creation time and optionally define condition (See 'Expression syntax' for other expressions).", canContainExpression: true); + public static readonly KeyValuePairOptionValue FileProperty_ModifiedTime = KeyValuePairOptionValue.Create("modified-time", "", shortKey: "mt", helpValue: "m[odified-]t[ime]=", description: "Show file's modified time and optionally define condition (See 'Expression syntax' for other expressions).", canContainExpression: true); + public static readonly KeyValuePairOptionValue FileProperty_Size = KeyValuePairOptionValue.Create("size", "", helpValue: "s[ize]=", description: "Show file's size and optionally define condition (See 'Expression syntax' for other expressions).", canContainExpression: true); public static readonly KeyValuePairOptionValue Group = KeyValuePairOptionValue.Create("group", "", shortKey: "g"); public static readonly KeyValuePairOptionValue Length = KeyValuePairOptionValue.Create("length", "", shortKey: "", description: "Include matches whose length matches the expression (See 'Expression syntax' for other expressions).", canContainExpression: true); diff --git a/src/CommandLine/Properties/launchSettings.json b/src/CommandLine/Properties/launchSettings.json index ace3e3b8..aa957180 100644 --- a/src/CommandLine/Properties/launchSettings.json +++ b/src/CommandLine/Properties/launchSettings.json @@ -2,7 +2,7 @@ "profiles": { "CommandLine": { "commandName": "Project", - "commandLineArgs": "help -v d" + "commandLineArgs": "spellcheck \"C:\\code\\jp\\josefpihrt.github.io\\docs\" --min-word-length 3 --max-word-length 35 -o c:/spellcheck/tmp.txt v=n -v d -i .git,.vs l li e ne --words c:/spellcheck/words -e txt,md,mdx,rst,adoc --interactive" } } } \ No newline at end of file diff --git a/src/Core/Properties/AssemblyInfo.cs b/src/Core/Properties/AssemblyInfo.cs index eb0153d3..d3952927 100644 --- a/src/Core/Properties/AssemblyInfo.cs +++ b/src/Core/Properties/AssemblyInfo.cs @@ -7,3 +7,4 @@ [assembly: InternalsVisibleTo("Orang, PublicKey=00240000048000009400000006020000002400005253413100040000010001009ff202171ab25d708192b490c52c1a373c74a2849c734fd9f545bfedc92b61d4e10d356cd26213ef6d96af669a9b570cd6277d590c338cfc00ccc9a15d6ad5b08ac3a8a09db3eae536d653f4acb9c7e992162129b67b4bc72c08af7d67a48ecde99c53a5d2cd44b1e8179368f6db2ec7665061e3ef4029703df4b49952bd0de4")] [assembly: InternalsVisibleTo("Orang.CommandLine.Core, PublicKey=00240000048000009400000006020000002400005253413100040000010001009ff202171ab25d708192b490c52c1a373c74a2849c734fd9f545bfedc92b61d4e10d356cd26213ef6d96af669a9b570cd6277d590c338cfc00ccc9a15d6ad5b08ac3a8a09db3eae536d653f4acb9c7e992162129b67b4bc72c08af7d67a48ecde99c53a5d2cd44b1e8179368f6db2ec7665061e3ef4029703df4b49952bd0de4")] [assembly: InternalsVisibleTo("Orang.FileSystem, PublicKey=00240000048000009400000006020000002400005253413100040000010001009ff202171ab25d708192b490c52c1a373c74a2849c734fd9f545bfedc92b61d4e10d356cd26213ef6d96af669a9b570cd6277d590c338cfc00ccc9a15d6ad5b08ac3a8a09db3eae536d653f4acb9c7e992162129b67b4bc72c08af7d67a48ecde99c53a5d2cd44b1e8179368f6db2ec7665061e3ef4029703df4b49952bd0de4")] +[assembly: InternalsVisibleTo("Orang.Spelling, PublicKey=00240000048000009400000006020000002400005253413100040000010001009ff202171ab25d708192b490c52c1a373c74a2849c734fd9f545bfedc92b61d4e10d356cd26213ef6d96af669a9b570cd6277d590c338cfc00ccc9a15d6ad5b08ac3a8a09db3eae536d653f4acb9c7e992162129b67b4bc72c08af7d67a48ecde99c53a5d2cd44b1e8179368f6db2ec7665061e3ef4029703df4b49952bd0de4")] diff --git a/src/FileSystem/FileSystem/FileSystemHelpers.cs b/src/FileSystem/FileSystem/FileSystemHelpers.cs index dadf7213..904e6979 100644 --- a/src/FileSystem/FileSystem/FileSystemHelpers.cs +++ b/src/FileSystem/FileSystem/FileSystemHelpers.cs @@ -428,6 +428,15 @@ public static int GetExtensionIndex(string path) return path.Length; } + public static bool HasExtension(string path, string extension) + { + int index = GetExtensionIndex(path); + + return (index >= 0) + && index < path.Length - 1 + && string.CompareOrdinal(path, index + 1, extension, 0, extension.Length) == 0; + } + public static bool IsDirectorySeparator(char ch) { return ch == Path.DirectorySeparatorChar diff --git a/src/FileSystem/IsExternalInit.cs b/src/FileSystem/IsExternalInit.cs new file mode 100644 index 00000000..f310729a --- /dev/null +++ b/src/FileSystem/IsExternalInit.cs @@ -0,0 +1,10 @@ +// Copyright (c) Josef Pihrt. All rights reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace System.Runtime.CompilerServices; + +using global::System.ComponentModel; + +[EditorBrowsable(EditorBrowsableState.Never)] +internal static class IsExternalInit +{ +}