Skip to content

Commit

Permalink
Support highlight lines in code snippet
Browse files Browse the repository at this point in the history
  • Loading branch information
superyyrrzz committed Apr 18, 2016
1 parent 8e8f013 commit 27e88f3
Show file tree
Hide file tree
Showing 16 changed files with 203 additions and 19 deletions.
5 changes: 4 additions & 1 deletion Documentation/spec/docfx_flavored_markdown.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,12 @@ Allows you to insert code with code language specified. The content of specified
* __`<queryoption>`__ and __`<queryoptionvalue>`__ are used together to retrieve part of the code snippet file in the line range or tag name way. We have 2 query string options to represent these two ways:

| | query string using `#` | query string using `?`
|--------------------------|----------------------------------------|-------------
|--------------------------|----------------------------------------|-----------------------------------------------
| 1. line range | `#L{startlinenumber}-L{endlinenumber}` | `?start={startlinenumber}&end={endlinenumber}`
| 2. tagname | `#{tagname}` | `?name={tagname}`
| 3. multiple region range | _Unsupported_ | `?range={rangequerystring}`
| 4. highlight lines | _Unsupported_ | `?highlight={rangequerystring}`
* In `?` query string, the whole file will be included if none of the first three option is specified.
* __`<title>`__ can be omitted.

#### Code Snippet Sample
Expand All @@ -128,6 +130,7 @@ Allows you to insert code with code language specified. The content of specified
[!code[Main](index.xml?start=5&end=9)]
[!code-javascript[Main](../jquery.js?name=testsnippet)]
[!code[Main](index.xml?range=2,5-7,9-) "This includes the lines 2, 5, 6, 7 and lines 9 to the last line"]
[!code[Main](index.xml?highlight=2,5-7,9-) "This includes the whole file with lines 2,5-7,9- highlighted"]
```

#### Tag Name Representation in Code Snippet Source File
Expand Down
2 changes: 1 addition & 1 deletion RELEASENOTE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ Version Notes (Current Version: v1.8)
=======================================
v1.8 (Pre-Release)
-----------

1. Support multiple region selection and highlight code lines in [Code Snippet](http://dotnet.github.io/docfx/spec/docfx_flavored_markdown.html#code-snippet) (https://github.com/dotnet/docfx/issues/189)

v1.7
-----------
Expand Down
6 changes: 6 additions & 0 deletions src/Microsoft.DocAsCode.Dfm/DfmCodeExtractor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,18 @@ public DfmExtractCodeResult ExtractFencesCode(DfmFencesBlockToken token, string
includedLines.Add(line);
}

if (!token.PathQueryOption.ValidateHighlightLines(includedLines.Count))
{
Logger.LogWarning(token.PathQueryOption.ErrorMessage);
}

int indentLength = (from line in includedLines
where !string.IsNullOrEmpty(line) && !string.IsNullOrWhiteSpace(line)
select (int?)DfmCodeExtractorHelper.GetIndentLength(line)).Min() ?? 0;
return new DfmExtractCodeResult
{
IsSuccessful = true,
ErrorMessage = token.PathQueryOption.ErrorMessage,
FencesCodeLines = (indentLength == 0 ? includedLines : includedLines.Select(s => Regex.Replace(s, string.Format(RemoveIndentSpacesRegexString, indentLength), string.Empty))).ToArray()
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,52 @@ namespace Microsoft.DocAsCode.Dfm

public abstract class DfmFencesBlockPathQueryOption : IDfmFencesBlockPathQueryOption
{
public string HighlightLines { get; set; }

public string ErrorMessage { get; protected set; }

public abstract bool ValidateAndPrepare(string[] lines, DfmFencesBlockToken token);

public abstract IEnumerable<string> GetQueryLines(string[] lines);

public bool ValidateHighlightLines(int totalLines)
{
if (string.IsNullOrEmpty(HighlightLines)) return true;
var ranges = HighlightLines.Split(',');
int? startLine, endLine;
int tempStartLine, tempEndLine;
foreach (var range in ranges)
{
var match = DfmFencesBlockRule._dfmFencesRangeQueryStringRegex.Match(range);
if (match.Success)
{
// consider region as `{startlinenumber}-{endlinenumber}`, in which {endlinenumber} is optional
startLine = int.TryParse(match.Groups["start"].Value, out tempStartLine) ? tempStartLine : (int?)null;
endLine = int.TryParse(match.Groups["start"].Value, out tempEndLine) ? tempEndLine : (int?)null;
}
else
{
// consider region as a sigine line number
if (int.TryParse(range, out tempStartLine))
{
startLine = tempStartLine;
endLine = startLine;
}
else
{
ErrorMessage = $"Illegal range {range} in query parameter `highlight`";
return false;
}
}
if (!CheckLineRange(totalLines, startLine, endLine))
{
ErrorMessage = ErrorMessage + " in query parameter `highlight`";
return false;
}
}
return true;
}

protected bool CheckLineRange(int totalLines, int? startLine, int? endLine)
{
if (startLine == null && endLine == null)
Expand All @@ -35,7 +75,7 @@ protected bool CheckLineRange(int totalLines, int? startLine, int? endLine)

if (startLine > totalLines)
{
ErrorMessage = $"Start line '{startLine}' execeeds total file lines '{totalLines}'";
ErrorMessage = $"Start line {startLine} execeeds total lines {totalLines}";
return false;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Microsoft.DocAsCode.Dfm
{
using System.Collections.Generic;

public class FullFileBlockPathQueryOption : DfmFencesBlockPathQueryOption
{
public override bool ValidateAndPrepare(string[] lines, DfmFencesBlockToken token)
{
return true;
}

public override IEnumerable<string> GetQueryLines(string[] lines)
{
foreach (var line in lines)
{
yield return line;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@ namespace Microsoft.DocAsCode.Dfm

public interface IDfmFencesBlockPathQueryOption
{
string HighlightLines { get; set; }

string ErrorMessage { get; }

bool ValidateAndPrepare(string[] lines, DfmFencesBlockToken token);

bool ValidateHighlightLines(int totalLines);

IEnumerable<string> GetQueryLines(string[] lines);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,15 @@ namespace Microsoft.DocAsCode.Dfm
using System;
using System.Collections.Generic;

class LineRangeBlockPathQueryOption : DfmFencesBlockPathQueryOption
public class LineRangeBlockPathQueryOption : DfmFencesBlockPathQueryOption
{
public int? StartLine { get; set; }

public int? EndLine { get; set; }

public override bool ValidateAndPrepare(string[] lines, DfmFencesBlockToken token)
{
if (!CheckLineRange(lines.Length, StartLine, EndLine))
{
return false;
}

return true;
return CheckLineRange(lines.Length, StartLine, EndLine);
}

public override IEnumerable<string> GetQueryLines(string[] lines)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace Microsoft.DocAsCode.Dfm
using System;
using System.Collections.Generic;

class MultipleLineRangeBlockPathQueryOption : DfmFencesBlockPathQueryOption
public class MultipleLineRangeBlockPathQueryOption : DfmFencesBlockPathQueryOption
{
public List<Tuple<int?, int?>> LinePairs { get; set; } = new List<Tuple<int?, int?>>();

Expand Down
5 changes: 4 additions & 1 deletion src/Microsoft.DocAsCode.Dfm/DfmRendererHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,11 @@ public static string GetRenderedFencesBlockString(DfmFencesBlockToken token, Opt
var lang = string.IsNullOrEmpty(token.Lang) ? null : $" class=\"{options.LangPrefix}{token.Lang}\"";
var name = string.IsNullOrEmpty(token.Name) ? null : $" name=\"{StringHelper.HtmlEncode(token.Name)}\"";
var title = string.IsNullOrEmpty(token.Title) ? null : $" title=\"{StringHelper.HtmlEncode(token.Title)}\"";
var highlight = string.IsNullOrEmpty(token.PathQueryOption?.HighlightLines)
? null
: $" highlight-lines=\"{StringHelper.HtmlEncode(token.PathQueryOption.HighlightLines)}\"";

renderedCodeLines = $"<pre><code{lang}{name}{title}>{StringHelper.HtmlEncode(string.Join("\n", codeLines))}\n</code></pre>";
renderedCodeLines = $"<pre><code{lang}{name}{title}{highlight}>{StringHelper.HtmlEncode(string.Join("\n", codeLines))}\n</code></pre>";
}

return $"{renderedErrorMessage}{renderedCodeLines}";
Expand Down
1 change: 1 addition & 0 deletions src/Microsoft.DocAsCode.Dfm/Microsoft.DocAsCode.Dfm.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
<Compile Include="CodeSnippetExtractor\RecursiveNameCodeSnippetExtractor.cs" />
<Compile Include="DfmCodeExtractorHelper.cs" />
<Compile Include="DfmFencesBlockPathQueryOptions\DfmFencesBlockPathQueryOption.cs" />
<Compile Include="DfmFencesBlockPathQueryOptions\FullFileBlockPathQueryOption.cs" />
<Compile Include="DfmFencesBlockPathQueryOptions\MultipleLineRangeBlockPathQueryOption.cs" />
<Compile Include="DfmFencesBlockPathQueryOptions\TagNameBlockPathQueryOption.cs" />
<Compile Include="DfmFencesBlockPathQueryOptions\LineRangeBlockPathQueryOption.cs" />
Expand Down
14 changes: 8 additions & 6 deletions src/Microsoft.DocAsCode.Dfm/Rules/DfmFencesBlockRule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ namespace Microsoft.DocAsCode.Dfm
{
using System;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.Web;

using Microsoft.DocAsCode.MarkdownLite;
Expand All @@ -16,6 +15,7 @@ public class DfmFencesBlockRule : IMarkdownRule
private const string EndLineQueryStringKey = "end";
private const string TagNameQueryStringKey = "name";
private const string RangeQueryStringKey = "range";
private const string HighlightLinesQueryStringKey = "highlight";
private const char RegionSeparatorInRangeQueryString = ',';

public string Name => "RestApiFences";
Expand Down Expand Up @@ -71,16 +71,17 @@ private static IDfmFencesBlockPathQueryOption ParsePathQueryString(string queryO
var start = collection[StartLineQueryStringKey];
var end = collection[EndLineQueryStringKey];
var range = collection[RangeQueryStringKey];
var highlight = collection[HighlightLinesQueryStringKey];
if (tagName != null)
{
return new TagNameBlockPathQueryOption { TagName = tagName };
return new TagNameBlockPathQueryOption { TagName = tagName , HighlightLines = highlight};
}
else if (range != null)
{
var regions = range.Split(RegionSeparatorInRangeQueryString);
if (regions != null)
{
var option = new MultipleLineRangeBlockPathQueryOption();
var option = new MultipleLineRangeBlockPathQueryOption { HighlightLines = highlight };
foreach (var region in regions)
{
var match = _dfmFencesRangeQueryStringRegex.Match(region);
Expand All @@ -107,12 +108,13 @@ private static IDfmFencesBlockPathQueryOption ParsePathQueryString(string queryO
return new LineRangeBlockPathQueryOption
{
StartLine = int.TryParse(start, out startLine) ? startLine : (int?)null,
EndLine = int.TryParse(end, out endLine) ? endLine : (int?)null
EndLine = int.TryParse(end, out endLine) ? endLine : (int?)null,
HighlightLines = highlight
};
}
return null;
return new FullFileBlockPathQueryOption { HighlightLines = highlight };
}
return null;
return new FullFileBlockPathQueryOption();
}
}
}
7 changes: 6 additions & 1 deletion src/docfx.website.themes/default/styles/docfx.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License.txt in the project root for license information. */
/* Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License.txt in the project root for license information. */
html,
body {
font-family: 'Segoe UI', Tahoma, Helvetica, sans-serif;
Expand Down Expand Up @@ -776,3 +776,8 @@ footer {
display: none;
}
}

/* For code snippet line highlight */
pre > code .line-highlight {
background-color: #ffffcc;
}
37 changes: 37 additions & 0 deletions src/docfx.website.themes/default/styles/docfx.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,43 @@ $(function () {
var show = 'show';
var hide = 'hide';

// Line highlight for code snippet
(function () {
$('pre code[highlight-lines]').each(function(i, block) {
if (block.innerHTML === "") return;
var lines = block.innerHTML.split('\n');

queryString = block.getAttribute('highlight-lines');
if (!queryString) return;

var ranges = queryString.split(',');
for (var j = 0, range; range = ranges[j++];) {
var found = range.match(/^(\d+)\-(\d+)?$/);
if (found) {
// consider region as `{startlinenumber}-{endlinenumber}`, in which {endlinenumber} is optional
var start = +found[1];
var end = +found[2];
if (isNaN(end) || end > lines.length) {
end = lines.length;
}
} else {
// consider region as a sigine line number
if (isNaN(range)) continue;
var start = +range;
var end = start;
}
if (start <= 0 || end <= 0 || start > end || start > lines.length) {
// skip current region if invalid
continue;
}
lines[start - 1] = '<span class="line-highlight">' + lines[start - 1];
lines[end - 1] = lines[end - 1] + '</span>';
}

block.innerHTML = lines.join('\n');
});
})();

//Adjust the position of search box in navbar
(function () {
autoCollapse();
Expand Down
Binary file modified src/docfx/Template/default.zip
Binary file not shown.
Binary file modified src/docfx/Template/iframe.html.zip
Binary file not shown.
65 changes: 65 additions & 0 deletions test/Microsoft.DocAsCode.Dfm.Tests/DocfxFlavoredMarkdownTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,71 @@ static void Main(string[] args)
string s = &quot;test&quot;;
int i = 100;
}
</code></pre>")]
[InlineData(@"[!code-csharp[Main](Program.cs?highlight=1)]", @"<pre><code class=""lang-csharp"" name=""Main"" highlight-lines=""1"">namespace ConsoleApplication1
{
// &lt;namespace&gt;
using System;
using System.Collections.Generic;
using System.IO;
// &lt;/namespace&gt;
// &lt;snippetprogram&gt;
class Program
{
static void Main(string[] args)
{
string s = &quot;test&quot;;
int i = 100;
}
}
// &lt;/snippetprogram&gt;
#region Helper
internal static class Helper
{
#region Foo
public static void Foo()
{
}
#endregion
}
#endregion
}
</code></pre>")]
[InlineData(@"[!code[Main](Program.cs?start=5&end=9&highlight=1 ""This is root"")]", @"<pre><code name=""Main"" title=""This is root"" highlight-lines=""1"">using System.Collections.Generic;
using System.IO;
// &lt;/namespace&gt;
// &lt;snippetprogram&gt;
</code></pre>")]
[InlineData(@"[!code[Main](Program.cs?name=Helper&highlight=1 ""This is root"")]", @"<pre><code name=""Main"" title=""This is root"" highlight-lines=""1"">internal static class Helper
{
public static void Foo()
{
}
}
</code></pre>")]
[InlineData(@"[!code[Main](Program.cs?range=1-2,10,20-21,29-&highlight=1-2,7- ""This is root"")]", @"<pre><code name=""Main"" title=""This is root"" highlight-lines=""1-2,7-"">namespace ConsoleApplication1
{
class Program
#region Helper
internal static class Helper
#endregion
}
</code></pre>")]
[InlineData(@"[!code[Main](Program.cs?range=1,21,24-26,1,10,12-16&highlight=8-12 ""This is root"")]", @"<pre><code name=""Main"" title=""This is root"" highlight-lines=""8-12"">namespace ConsoleApplication1
internal static class Helper
public static void Foo()
{
}
namespace ConsoleApplication1
class Program
static void Main(string[] args)
{
string s = &quot;test&quot;;
int i = 100;
}
</code></pre>")]
public void TestDfmFencesBlockLevelWithQueryString(string fencesPath, string expectedContent)
{
Expand Down

0 comments on commit 27e88f3

Please sign in to comment.