怎樣在 Markdown 中使程式程式碼帶上行號

黃志斌發表於2013-03-24

在圖靈社群使用 Markdown 寫文章時,如果在一段文字的每行開頭加上四個空格,或者一個製表符(Tab),這段文字就會被視為程式程式碼。這樣,就會自動識別所用的程式語言,進行程式碼染色,語法高亮顯示。但是,如果這段程式很長的話,就有兩個小問題:

  1. 每行的開頭要加上空格或製表符,很麻煩。
  2. 如果要顯示行號的話,就更麻煩了。

因此,我用 C# 語言寫了小程式,建設一個 ASP.NET 4 網站來解決上述兩個麻煩:

1

2

3

在這個網頁中:

  • Line Count 核取方塊表示是否需要加上行號。
  • Prefix 中的的 SpaceTab 無線按鈕讓你選擇每行開頭是增加空格還是製表符。
  • Prefix Count 文字框讓你輸入縮排的層次。預設是縮排一層 。但是如果遇到在有序列表或無序列表中的程式程式碼,就需要縮排兩層,甚至更多層了。

這個網站的總體結構如下所示:

4

網站的配置檔案 Web.config 如下所示:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.web>
    <httpRuntime requestValidationMode="2.0" />
    <globalization requestEncoding="utf-8" responseEncoding="utf-8" />
  </system.web>
</configuration>

網站的 Web 頁面檔案 CodeFormat.aspx 如下所示:

<%@ Page validateRequest="false" Language="C#" inherits="Skyiv.Ben.Web.CodeFormatPage" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 
  <title>Code Format</title>
</head>
<body>
  <form id="form1" runat="server">
  <asp:Button Onclick="Submit" Text="Submit" Runat="Server" />
  <span style="background-color:LightBlue;">
    <asp:CheckBox Id="chkLineCount" Text="Line Count" Checked="True" Runat="Server" />
    &nbsp;
  </span>
  &nbsp;
  <span style="background-color:LightBlue;">
    &nbsp;Prefix:
    <asp:RadioButton Id="rbnSpace" Text="Space" Checked="True"
      GroupName="Prefix" Runat="Server" />
    <asp:RadioButton Id="rbnTab" Text="Tab"
      GroupName="Prefix" Runat="Server" />
    &nbsp;
  </span>
  &nbsp;
  <span style="background-color:LightBlue;">
    &nbsp;Prefix Count:
    <asp:TextBox Runat="Server" Id="tbxLevel" Text="1" Columns="2" MaxLength="1" />
    &nbsp;
  </span>
  <hr />
  <div>
  <asp:TextBox Runat="Server" Id="tbxInput" Wrap="False"
    TextMode="MultiLine" Columns="80" Rows="10" />
  <br />
  <asp:TextBox Runat="Server" Id="tbxOutput" ReadOnly="True" Wrap="False"
    TextMode="MultiLine" BackColor="LightBlue" Columns="80" Rows="10" />
  </div>
  </form>
</body>
</html>

以及對應的後臺 C# 程式碼 CodeFormat.aspx.cs:

 1: using System;
 2: using System.IO;
 3: using System.Web;
 4: using System.Web.UI;
 5: using System.Web.UI.WebControls;
 6: using Skyiv.Utils;
 7: 
 8: namespace Skyiv.Ben.Web
 9: {
10:   public class CodeFormatPage : Page
11:   {
12:     protected TextBox tbxInput;
13:     protected TextBox tbxOutput;
14:     protected TextBox tbxLevel;
15:     protected CheckBox chkLineCount;
16:     protected RadioButton rbnTab;
17:     
18:     protected void Page_Load(object sender, EventArgs e)
19:     {
20:       tbxOutput.Text = string.Format(" OS: {1} ({2}-bit){0}CLR: {3}",
21:         Environment.NewLine, Environment.OSVersion,
22:         Environment.Is64BitOperatingSystem ? 64 : 32,
23:         Environment.Version);
24:     }
25: 
26:     protected void Submit(object sender, EventArgs e)
27:     {
28:       var writer = new StringWriter();
29:       new CodeFormat(new StringReader(tbxInput.Text),
30:         writer).Run(chkLineCount.Checked, rbnTab.Checked, GetLevel(tbxLevel.Text));
31:       tbxOutput.Text = writer.ToString();
32:     }
33:     
34:     int GetLevel(string str)
35:     {
36:       int n;
37:       if (!int.TryParse(str, out n)) n = 1;
38:       return Math.Min(5, Math.Max(0, n));
39:     }
40:   }
41: }

上述程式中:

  • 第 34 至 39 行的 GetLevel 方法讀取 Prefix Count 文字框中的縮排層次,返回結果限制在 0 到 5 之間。
  • 第 26 至 32 行的 Submit 方法在 Web 頁面中的 Submit 按鈕被點選時被呼叫。
  • 第 29 至 30 行呼叫 CodeFormat 類的 Run 方法對程式程式碼進行格式化(加行號、行首空格等)。

下面就是 CodeFormat 類的源程式程式碼 CodeFormat.cs:

 1: using System;
 2: using System.IO;
 3: using System.Collections.Generic;
 4: 
 5: namespace Skyiv.Utils
 6: {
 7:   sealed class CodeFormat
 8:   {
 9:     TextReader reader;
10:     TextWriter writer;
11: 
12:     public CodeFormat(TextReader reader, TextWriter writer)
13:     {
14:       this.reader = reader;
15:       this.writer = writer;
16:     }
17:   
18:     public void Run(bool hasCount, bool isTab, int level)
19:     {
20:       Write(Read(), hasCount, isTab, level);
21:     }
22:     
23:     List<string> Read()
24:     {
25:       var lines = new List<string>();
26:       for (string s; (s = reader.ReadLine()) != null; ) lines.Add(s);
27:       return lines;
28:     }
29:     
30:     void Write(List<string> lines, bool hasCount, bool isTab, int level)
31:     {
32:       var prefix = "".PadLeft((isTab ? 1 : 4) * level, isTab ? '\t' : ' ');
33:       var format = "{0}" + (hasCount ? "{1," +
34:         lines.Count.ToString().Length + "}: " : "") + "{2}";
35:       var count = 0;
36:       foreach (var line in lines)
37:         writer.WriteLine(format, prefix, ++count, line);
38:     }
39:   }
40: }

上述程式中:

  • 第 9 至 10 行的 TextReaderTextWriter 分別用於讀取資料和輸出格式化後的結果,這兩個類是抽象基類。
  • 在這個網站中,是使用 StringReaderStringWriter 派生類,對應於 Web 頁面的 tbxInputtbxOutput 文字框。
  • 如果使用 StreamReaderStreamWriter 派生類,就可以從輸入流讀取資料,寫到輸出流中。
  • 如果使用 Console.InConsole.Out,就可以從標準輸入讀取資料,寫到標準輸出。
  • 第 23 至 28 行的 Read 方法讀取資料到記憶體的 List<string> 資料結構中。
  • 第 30 至 38 行的 Writer 方法將記憶體中的資料格式化後寫出去。
  • 第 32 行根據 isTablevel 引數決定程式程式碼資料每行的字首。
  • 第 33 至 34 行根據 hasCount 引數決定行號的內容。
  • 第 34 行的 lines.Count.ToString().Length 是行號所佔的寬度。
  • 第 36 至 37 行的迴圈逐行格式化資料。

最後是 Makefile:

CSC = dmcs
DLL1 = -r:System.Web.dll

../bin/CodeFormat.dll: CodeFormat.aspx.cs CodeFormat.cs
    $(CSC) -out:$@ -t:library $(DLL1) CodeFormat.aspx.cs CodeFormat.cs

有了上面的源程式後,執行 make 命令編譯整個網站:

src$ make
dmcs -out:../bin/CodeFormat.dll -t:library -r:System.Web.dll CodeFormat.aspx.cs CodeFormat.cs

這就大功告成了。


在 Linux 作業系統中,也可使用以下命令:

$ nl -w6 -ba -s': ' a.c

相關文章