[搬運] 將 Visual Studio 的代碼片斷導出到 VS Code

原文 : A Visual Studio to Visual Studio Code Snippet Converter
做者 : Rick Strahl
譯者 : 張蘅水css

導語

和原文做者同樣,水弟我如今也是使用 VS Code 和 Rider 做爲主力開發工具,尤爲是 VS Code 能夠跨平臺,又有豐富的插件支持和多種編程語言支持。當我從 VS 轉移到以 VS Code 的開發過程當中,遇到的最大問題就是代碼提示的不完善(被 VS 和 R# 調教壞了,總想按 tab 鍵)。當我看到原文做者經過從 VS 中導出代碼片斷到 VS Code 時,瞬間被吸引到了。 雖然在不知不覺中用了 VS 自帶的代碼片斷,但我歷來沒有想過要自定義專屬的代碼片斷,最多也是製做用於項目的模板(方便建立特定的類型文件)。雖然導出到 Rider 不是很完美,但 Rider 自帶了 R#,對這方面的需求仍是不多的。html


譯文:

Visual Studio 內置了很是好用的代碼片斷工具,多年來我一直在使用它來建立大量有用的擴展片斷,使個人平常開發更容易。我有不少 C# 代碼片斷,但更多的是用於 HTML 、自定義的 Bootstrap 代碼片斷,乃至複雜的 HTML 控件代碼段。偶爾也會用到 JavaScript 、XAML 甚至Powershell 。git

在過去的幾年裏,我愈來愈多地使用其餘工具與 Visual Studio 結合使用。特別是Visual Studio CodeJetBrains Ridergithub

在多年的使用 Visual Studio 中,我已經累積了 130 多個代碼片斷。每當我在其餘開發環境中工做時 ( VS Code 或者 Rider),我真的很須要他們,特別是要寫一大段 HTML的時候,老是要去痛苦地去對應的文檔站點查找。使用代碼片斷功能,只需幾回擊鍵就會自動填充我自定義的特定代碼,天天可節省大量時間。web

因此我很須要代碼片斷功能,有時我打開 Visual Studio 只是爲了找到須要的 HTML 的代碼片斷,而後將它們粘貼回 VS Code 或 Rider。雖然繁瑣,可是仍然從文檔網站中複製代碼,而後手動修改代碼來得便捷。若是能在每一個對應的開發環境中直接執行代碼片斷的功能,那就太好了!shell

所以,在過去的幾個週末,我作了一個將 Visual Studio 中的代碼片斷導出到 VS Code 中的小工具,同時儘可能能導出到 JetBrains Rider 。編程

若是你感興趣,能夠在GitHub上找到代碼:json

另外說一句,這還只是一個菜鳥項目,並不能保證它支持全部類型的的代碼片斷。只是我本身擁有的 137 個代碼片斷都完美地移植到 VS Code,而且可以運行。同時我還能夠從新導出, 輕鬆地導出新建立的代碼片斷,這樣就能夠對比和更新了。數組

對於 Rider 而言,操做起來更爲複雜,由於 Rider 有一種瘋狂的機制,能夠將模板存儲在內部的單個配置文件中。它還爲 .NET相關的片斷 (C#、VB、F#、Razor、ASP.NET )和 基於 Web ( html、css、js 等)的代碼片斷使用了多個徹底不一樣的存儲引擎。因此工具目前僅支持一次性導出 .NET 相關代碼段,由於 Rider 中基於 GUID 的密鑰系統不容許在沒有 GUID 的狀況下查找現有代碼段。後面咱們再詳細介紹。app

代碼片斷轉換器

The Snippet Converter

你能夠經過藉助 .NET 全局工具 (.NET Global SDK Tool ),使用 Nuget 下載和運行代碼片斷轉換器:

dotnet tool install --global dotnet-snippetconverter

若是您不想安裝並只運行該工具,您能夠克隆或下載Github倉庫,而後:

cd .\SnippetConverter\
dotnet run

安裝後, 能夠經過指向文件夾或單個文件將 Visual Studio 中的代碼片斷批量或單獨轉換爲 VS Code 支持的代碼片斷。

snippetconverter ~2017 -r -d

或者,您能夠像下面這張屏幕截圖那樣指定輸出文件:

image

有幾個選項可用於轉換單個片斷和文件夾,使用前綴,遞歸文件夾,輸出生成文件的路徑等:

Syntax:
-------
SnippetConverter <sourceFileOrDirectory> -o <outputFile> 
                 --mode --prefix --recurse --display

Commands:
---------
HELP || /?          This help display           

Options:
--------
sourceFileOrDirectory  Either an individual snippet file, or a source folder
                       Optional special start syntax using `~` to point at User Code Snippets folder:
                       ~      -  Visual Studio User Code Snippets folder (latest version installed)
                       ~2017  -  Visual Studio User Code Snippets folder (specific VS version 2019-2012)                       

-o <outputFile>        Output file where VS Code snippets are generated into (ignored by Rider)   
                       Optional special start syntax using `~` to point at User Code Snippets folder:
                       %APPDATA%\Code\User\snippets\ww-my-codesnippets.code-snippets
                       ~\ww-my-codesnippets.code-snippets
                       if omitted generates `~\exported-visualstudio.code-snippets`
                       
-m,--mode              vs-vscode  (default)
                       vs-rider   experimental - (C#,VB.NET,html only)
-d                     display the target file in Explorer
-r                     if specifying a source folder recurses into child folders
-p,--prefix            snippet prefix generate for all snippets exported
                       Example: `ww-` on a snippet called `ifempty` produces `ww-ifempty`

Examples:
---------
# vs-vscode: Individual Visual Studio Snippet
SnippetConverter "~2017\Visual C#\My Code Snippets\proIPC.snippet" 
                 -o "~\ww-csharp.code-snippets" -d

# vs-vscode: All snippets in a folder user VS Snippets and in recursive child folers
SnippetConverter "~2017\Visual C#\My Code Snippets" -o "~\ww-csharp.code-snippets" -r -d

# vs-vscode: All the user VS Snippets and in recursive child folders
SnippetConverter ~2017\ -o "~\ww-all.code-snippets" -r -d

# vs-vscode: All defaults: Latest version of VS, all snippets export to  ~\visualstudio-export.code-snippets
SnippetConverter ~ -r -d --prefix ww-

# vs-rider: Individual VS Snippet
SnippetConverter "~2017\proIPC.snippet" -m vs-rider -d

# vs-rider: All VS Snippets in a folder
SnippetConverter "~2017\Visual C#\My Code Snippets" -m vs-rider -d

上面的用例應該足夠說明用途了。若是還想要了解更多信息,請接着往下看......

什麼是 VS Code 的代碼片斷

若是您不熟悉或不使用代碼片斷,那您並非少數人。它們在 Visual Studio 中幾乎是一個隱藏的功能,這是一個恥辱,由於它們是很是有用的生產力工具。不幸的是,Visual Studio 沒有任何有用的內置UI來建立這些片斷,所以大多數開發人員都沒有充分利用此功能。Visual Studio 只能蹩腳地點擊 ** 工具 - > 代碼片斷管理器 ** 菜單 ,除了一個查看器以外,它沒有其餘管理功能,僅僅是查看哪些片斷是可用的,沒有內置的方法來建立或編輯片斷,甚至跳轉到並查看代碼片斷。

可是,代碼片斷僅僅只是位於用戶目錄的 Documents 文件夾下的 XML 文件。它們很是容易建立和更新,僅僅是原始的 XML 文件,用 VS Code 等文本編輯器去作代碼片斷和高亮實在是很是簡單。儘管在 Visual Studio 中有一些提供 UI 操做的劣質插件,但它們每每比原始的代碼片斷文件更麻煩。

建立新代碼段的最佳方法是複製現有代碼段並對其進行修改以知足您的需求。

通常來講,代碼片斷位於 (水弟我是直接用 Everything搜索的):

<Documents>\Visual Studio 2017\Code Snippets

每種語言技術都有本身的子文件夾進行分組,但僅僅是文件夾上的區分而已。代碼片斷實際上經過 XML中的 Language 屬性肯定它們適用的語言。

Visual Studio在此位置附帶了許多代碼段,您可使用這些代碼段做爲新代碼段的模板進行學習。

<?xml version="1.0" encoding="utf-8"?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <CodeSnippet Format="1.0.0">
    <Header>
      <Title>Property with INotifyPropertyChange raised</Title>
      <Description>Control Property with Attributes</Description>
      <SnippetTypes>
        <SnippetType>Expansion</SnippetType>
      </SnippetTypes>
      <Shortcut>proIPC</Shortcut>
    </Header>
    <Snippet>
      <References />
      <Imports />
      <Declarations>
        <Literal Editable="true">
          <ID>name</ID>
          <Type></Type>
          <ToolTip>Property Name</ToolTip>
          <Default>MyProperty</Default>
          <Function></Function>
        </Literal>        
        <Literal Editable="true">
          <ID>type</ID>
          <Type></Type>
          <ToolTip>Property Type</ToolTip>
          <Default>string</Default>
          <Function></Function>
        </Literal>
      </Declarations>
      <Code Language="csharp" Kind="method decl" Delimiter="$"><![CDATA[public $type$ $name$
{
    get { return _$name$; }
    set
    {
        if (value == _$name$) return;
        _$name$ = value;
        OnPropertyChanged(nameof($name$));
    }
}        
private $type$ _$name$;
]]></Code>
    </Snippet>
  </CodeSnippet>
</CodeSnippets>

一旦文件存在或更新了,Visual Studio 無需重啓,就能當即發現並使用。在相關的 (如 C#) 編輯器中,立馬就能看到智能提示中的代碼段:

智能提示

它插入對應的模板並容許您編輯在模板中聲明的 $expr$ 佔位符:

EditSnippetInVS.png

這個 C# 代碼片斷示例, 是 VS 中最多見的語言。如您所見, XML文件中的 <Code> 節點定義模板文本,Shortcut 節點定義觸發提示的按鍵,你可使用在 <Declaration> 節點中使用相似 $txt$ 佔位符來定義參數,一樣的佔位符在多個地方出現也能同步更改。

對於我來講,最有用和最經常使用的代碼片斷時用於插入 HTML 代碼,特別是在定義 Bootstrap 結構或其餘很難記住語法的自定義控件。我喜歡在瀏覽文檔網站後建立一個對應的片斷,這樣就很方便使用。用多幾回,省下來的時間就賺翻了。花費幾分鐘設置模板能夠節省大量時間去輸入重複代碼,尤爲是您每次都要浪費時間查找相同的 Bootstrap 代碼時。😃

前綴以及代碼片斷包

Visual Studio Marketplace中還有許多可用的代碼片斷,您能夠安裝使用一整套預設的代碼片斷。例如,Bootstrap Snippet 包就內置了一堆以 bs- 爲前綴的代碼片斷。

代碼片斷包

即便您本身有專屬的代碼片斷,最好爲您的代碼片斷建立一個前綴,以便您能夠在智能提示的海洋中輕鬆地找到它們。我通常使用 ww- 做爲大多數代碼片斷的前綴。不幸的是,我本身沒有很好得遵循這個建議,仍是有很多代碼片斷沒有這麼作。

構建轉換器

由於我在 Visual Studio 大量使用了代碼片斷,因此我作了一個將 Visual Studio 中的代碼片斷遷移到 VS Code 中的小工具,同時儘可能能遷移到 JetBrains Rider 。

我想可能還有其餘人須要用到,因此我把它做爲 .NET Global Tool 控制檯應用程序發佈,以便快速安裝:

dotnet tool install dotnet-snippetconverter

您須要.NET Core 2.1 SDK或更高版本才能運行它。

如下示例命令將代碼片斷從 Visual Studio 遷移到 VS Code,稍後再討論遷移到 Rider 的事

安裝後,您可使用如下命令快速將全部 Visual Studio 代碼片斷轉換爲 VS Code 能夠接受的格式。

snippetconverter ~ -r -d

這將轉換最新安裝的 Visual Studio 版本(2017,2019等)中的全部代碼片斷,並在位於%appdata%\Code\User\snippets 路徑的 VS Code 的代碼文件夾中建立單獨的 visualstudio-exported.code-snippets 文件夾。

您還能夠導出特定 VS 版本的代碼片斷:

snippetconverter ~2017 -r -d

或特定文件夾:

snippetconverter "~2017\Visual C#\My Code Snippets" -r -d -o "~\ww-csharp.code-snippets"

其中輸入和輸出文件夾選項中的路徑都是可選的,示例中的~ 是物理片斷路徑的佔位符,會指向 Visual Studio(%Documents%\Visual Studio <year>\Code Snippets)和 VS Code(%appdata%\Code\User\Snippets\)中存放代碼片斷的基本位置,所以您沒必要每次都指定完整路徑。您高興的話,也可使用合格的全路徑。

最後,您還能夠導出單個文件:

snippetconverter "~2017\Visual C#\My Code Snippets\proIPC.snippet" -d -o "~\ww-csharp.code-snippets"

若是 VS Code 中已存在該代碼片斷,則會覆蓋更新,因此每次從新運行都會更新對應的代碼片斷。

運行遷移工具後,在VS Code 中經過前綴或者快捷方式就能夠當即使用:

在 Visual Studio 中多個佔位符輸入也是支持的:

同步代碼片斷

目前只支持從Visual Studio 單向 遷移到到 VS Code。這意味着若是要保持 Visual Studio 和 VS Code 之間的代碼段同步,最好是在 Visual Studio 中建立代碼片斷,而後經過此工具將它們遷移到 VS Code。

VS Code 中的代碼片斷

我以前討論過 Visual Studio Snippets 的代碼片斷格式,如今讓咱們看看 VS Code 中又是什麼樣的。

  • 存放在 %AppData\Code\User\snippets
  • 使用 JSON 格式化
  • 命名爲 lang.json
  • 或者是 <name>.code-snippet 的命名格式
  • 能夠包含一個或者多個代碼片斷

轉換器之因此導出爲 .code-snippet 文件格式,是由於使用 lang.json 很容易形成命名衝突。若是默認的 visualstudio-export.code-snippets 不能使用,則使用 -o 來指定輸出文件。

VS Code 代碼片斷文件是 JSON,它們看起來像:

{
  "proipc": {
    "prefix": "proipc",
    "scope": "csharp",
    "body": [
      "public ${2:string} ${1:MyProperty}",
      "{",
      "    get { return _${1:MyProperty}; }",
      "    set",
      "    {",
      "        if (value == _${1:MyProperty}) return;",
      "        _${1:MyProperty} = value;",
      "        OnPropertyChanged(nameof(${1:MyProperty}));",
      "    }",
      "}        ",
      "private ${2:string} _${1:MyProperty};",
      ""
    ],
    "description": "Control Property with Attributes"
  },
  "commandbase-object-declaration": {
    "prefix": "commandbase",
    "scope": "csharp",
    "body": [
      "        public CommandBase ${1:CommandName}Command { get; set;  }",
      "",
      "        void Command_${1:CommandName}()",
      "        {",
      "            ${1:CommandName}Command = new CommandBase((parameter, command) =>",
      "            {",
      "              $0",
      "            }, (p, c) => true);",
      "        }",
      ""
    ],
    "description": "Create a CommandBase implementation and declaration"
  } 
}

VS Code 的代碼模板在概念上更簡單,只有模板,前綴和範圍,以及使用字符串插值和約定來肯定如何定義佔位符。固然還有其餘字段能夠填充,但大多數值是可選的,對於從 Visual Studio 轉換過來的代碼片斷用不到。

您能夠在此處找到Visual Studio代碼段模板文檔:

可是實際上,本身手動建立模板,定義 JSON中的 body 屬性仍是有難度的,由於字符串可能只是一個字符串數組(yuk),也多是一個能夠輸入的類型。好在只是從 Visual Studio 代碼片斷轉換,仍是很容易生成對應的模板...

咦?導出到 Rider

轉換器某種程度上能夠適配到 Rider,但功能有限。由於Rider 使用使人抓狂的模式來存儲代碼片斷,使用 GUID 來標識的 XML 文件。

%USERPROFILE%\.Rider2018.2\config\resharper-host\GlobalSettingsStorage.DotSettings

讓咱們看看幾個導出的模板效果:

<root>
    <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=720E28E0ECFD4CA0B80F10DC82149BD4/Reformat/@EntryValue">True</s:Boolean>
    <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=720E28E0ECFD4CA0B80F10DC82149BD4/Shortcut/@EntryValue">proipc</s:String>
    <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=720E28E0ECFD4CA0B80F10DC82149BD4/ShortenQualifiedReferences/@EntryValue">True</s:Boolean>
    <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=720E28E0ECFD4CA0B80F10DC82149BD4/Scope/=C3001E7C0DA78E4487072B7E050D86C5/@KeyIndexDefined">True</s:Boolean>
    <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=720E28E0ECFD4CA0B80F10DC82149BD4/Scope/=C3001E7C0DA78E4487072B7E050D86C5/Type/@EntryValue">InCSharpFile</s:String>
    <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=720E28E0ECFD4CA0B80F10DC82149BD4/Text/@EntryValue">public $type$ $name$
{
    get { return _$name$; }
    set
    {
        if (value == _$name$) return;
        _$name$ = value;
        OnPropertyChanged(nameof($name$));
    }
}        
private $type$ _$name$;
    </s:String>
    <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=720E28E0ECFD4CA0B80F10DC82149BD4/Field/=name/@KeyIndexDefined">True</s:Boolean>
    <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=720E28E0ECFD4CA0B80F10DC82149BD4/Field/=name/Expression/@EntryValue">complete()</s:String>
    <s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=720E28E0ECFD4CA0B80F10DC82149BD4/Field/=name/Order/@EntryValue">0</s:Int64>
    <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=720E28E0ECFD4CA0B80F10DC82149BD4/Field/=type/@KeyIndexDefined">True</s:Boolean>
    <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=720E28E0ECFD4CA0B80F10DC82149BD4/Field/=type/Expression/@EntryValue">complete()</s:String>
    <s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=720E28E0ECFD4CA0B80F10DC82149BD4/Field/=type/Order/@EntryValue">1</s:Int64>

    <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=E88A906D39C741C0A3B8095C5063DADE/@KeyIndexDefined">True</s:Boolean>
    <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=E88A906D39C741C0A3B8095C5063DADE/Applicability/=Live/@EntryIndexedValue">True</s:Boolean>
    <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=E88A906D39C741C0A3B8095C5063DADE/Reformat/@EntryValue">True</s:Boolean>
    <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=E88A906D39C741C0A3B8095C5063DADE/Shortcut/@EntryValue">seterror</s:String>
    <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=E88A906D39C741C0A3B8095C5063DADE/ShortenQualifiedReferences/@EntryValue">True</s:Boolean>
    <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=E88A906D39C741C0A3B8095C5063DADE/Scope/=C3001E7C0DA78E4487072B7E050D86C5/@KeyIndexDefined">True</s:Boolean>
    <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=E88A906D39C741C0A3B8095C5063DADE/Scope/=C3001E7C0DA78E4487072B7E050D86C5/Type/@EntryValue">InCSharpFile</s:String>
    <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=E88A906D39C741C0A3B8095C5063DADE/Text/@EntryValue">      
        public string ErrorMessage {get; set; }

        protected void SetError()
        {
            this.SetError("CLEAR");
        }

        protected void SetError(string message)
        {
            if (message == null || message=="CLEAR")
            {
                this.ErrorMessage = string.Empty;
                return;
            }
            this.ErrorMessage += message;
        }

        protected void SetError(Exception ex, bool checkInner = false)
        {
            if (ex == null)
                this.ErrorMessage = string.Empty;

            Exception e = ex;
            if (checkInner)
                e = e.GetBaseException();

            ErrorMessage = e.Message;
        }
    </s:String>
    <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=E88A906D39C741C0A3B8095C5063DADE/Field/=busObject/@KeyIndexDefined">True</s:Boolean>
    <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=E88A906D39C741C0A3B8095C5063DADE/Field/=busObject/Expression/@EntryValue">complete()</s:String>
    <s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=E88A906D39C741C0A3B8095C5063DADE/Field/=busObject/Order/@EntryValue">0</s:Int64>
    <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=E88A906D39C741C0A3B8095C5063DADE/Field/=NewLiteral/@KeyIndexDefined">True</s:Boolean>
    <s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=E88A906D39C741C0A3B8095C5063DADE/Field/=NewLiteral/Expression/@EntryValue">complete()</s:String>
    <s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=E88A906D39C741C0A3B8095C5063DADE/Field/=NewLiteral/Order/@EntryValue">1</s:Int64>
</root>

使用這種瘋狂的格式,沒法分辨一組代碼片斷的開始和結束的位置。每一個代碼片斷都有多個 Key,加上 GUID 標識,這使得匹配現有的代碼段來判斷是否存在的目的幾乎不可能實現。

據我所知,沒有找到任何相關鍵值配置的文檔,也沒有如何存儲的文檔。頗有可能存在其餘存儲選項,但看起來 Rider 並無爲代碼片斷設置編輯功能。若是您有更好的開發人員文檔,請發表評論。

出於這個緣由,Rider 導入是一次性的,若是您導出兩次相同的片斷,它們就會翻倍。

爲了測試,我在 Rider 的導出文件中添加了一個標記鍵。而後,在我導入相同的代碼片斷時,我會刪除了以前添加的代碼片斷。很簡陋,也只是測試階段。若是相關的配置發生了變化,則可能會失效。

此格式僅適用於 Rider 支持的 .NET 特定代碼類型:.NET Languages,Razor 和包含 HTML 模板的 WebForms。其餘格式( JavaScript、HTML 、CSS)則使用徹底獨立的格式,我沒有精力在實現相關的功能。對於 Rider,我主要關心的是 C# 和 HTML 模板,能正常運行就行了。

只需導出特定文件夾,如 C# 文件夾或 HTML 代碼段,而不是批量導出整個代碼片斷文件夾。

SnippetConverter "~2017\Visual C#\My Code Snippets" -m vs-rider -d
SnippetConverter "~2017\Code Snippets\Visual Web Developer\My HTML Snippets" -m vs-rider -d

摘要

正如我前面提到的,全部這些都是很是簡陋,但對於將我所有的代碼片斷從 Visual Studio 導出到 Visual Studio Code 是徹底夠用的。對於 Rider, C# 和 HTML 代碼片斷導出也能夠作到,可是其餘類型(如 JavaScript、CSS)會出現異常。我只是看成我的工具,若是哪天有足夠的興趣的話,我會接着完善,可是很大程度是須要另外搞一個徹底獨立的轉換器。

我沒有測試全部的 Visual Studio 支持的文件類型,即便是VS 內置的代碼片斷也可能存在某些問題。保險一點,請不要批量導出全部代碼段,而是單獨導出每種類型的代碼片斷。

我仍是強烈建議使用前綴,由於能夠更容易地找到你的代碼片斷,並保持它們不受影響。

如今這個工具對於我來講已經足夠了,可是我很想知道我是不是少數幾個投身到代碼片斷轉換的人之一😃

相關資源

相關文章
相關標籤/搜索