LoginSignup
0
0

More than 3 years have passed since last update.

C# - VSCodeスニペットを読み込むツールを作ってみた

Last updated at Posted at 2019-12-01

C# よく使うusing 名前空間 / (Windows)Form テンプレ作った (Visual Studio使わない人向け)
の続き。

残存課題:

  • Jsonは順序性を気にしないものらしく、入力データと読み込んだときのデータの並びが変わってしまう。
  • メニューとかctrl+Sでの保存は未対応

"body" の中身の整形する機能だけのツールを作ったほうが使いやすい気がしてきた・・

環境

Windows10
※visuals studio等でwindows sdk入れてないとwindows.winmdファイルがなくてコンパイルできないっぽい

仕様概要

VisualStudioCodeのスニペットのJsonは、標準のJsonではないようで1、全体を読み込むと例外を吐いてしまうため、一部のみを編集対象とすることにした。

Json内の
// %%kob58im-ToolBegin%%
// %%kob58im-ToolEnd%%
に囲まれた部分をJsonとして扱い、読み込みます。(複数ペアは不可)

画面キャプチャ

image.png

やってた最中にはまったこと

・VSCodeのJsonは改行コードが\nのよう

・C#のRegexRegexOptions.Multiline指定時の$\r\nにはマッチしない(\nにマッチする)。
 参考: http://gushwell.ldblog.jp/archives/50221903.html

・Microsoft Docsにあるメンバの名前が間違っている?
 https://qiita.com/kob58im/items/a743bfcdf929c3ec6771

ソースコード


#define DEBUG

using System;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using Windows.Data.Json;// to use JsonValue class


public class MyJsonItemInfo
{
    public string Name{get;set;}
    public string Description{get;set;}
    public string Prefix{
        get{return String.Join(", ", _prefix);}
        set{_prefix = value.Split(new Char[]{','}).Select(x=>x.Trim()).ToArray();}
    }
    public string Body{
        get{return String.Join("\r\n", _body);}
        set{_body = Regex.Split(value,@"\r?\n");}
    }
    public string BodyFirstLine{get{return (_body.Length==0)?"":_body[0];}}
    public string[] BodyAsArray{get{return _body;}}
    public int Lines{get{return _body.Length;}}

    string[] _prefix;
    string[] _body;


    public MyJsonItemInfo(string keyName, JsonObject jo)
    {
        Name = keyName;

        JsonValue jvPrefix;
        _prefix = MyJsonAsStringArray("prefix", jo, out jvPrefix) ?? new string[]{};

        JsonValue jvDescription;
        Description = MyJsonAsString("description", jo, out jvDescription) ?? "";

        JsonValue jvBody;
        _body = MyJsonAsStringArray("body", jo, out jvBody) ?? new string[]{};
    }


    static string[] MyJsonAsStringArray(string keyName, JsonObject joItem, out JsonValue jv)
    {
        try {
            jv = joItem.GetNamedValue(keyName);
        }
        catch ( Exception ) {
            jv = null;
            return new string[]{};
        }

        if ( jv.ValueType == JsonValueType.String ) {
            return new string[]{jv.GetString()};
        }
        else if ( jv.ValueType == JsonValueType.Array ) {
            return MyJsonArrayToStringArray(jv.GetArray());
        }
        else {
            return new string[]{};
        }
    }

    static string MyJsonAsString(string keyName, JsonObject joItem, out JsonValue jv)
    {
        try {
            jv = joItem.GetNamedValue(keyName);
            // 第2引数にデフォルト値として JsonValue.CreateNullValue() を入れようとしたが、なぜか無い..
        }
        catch ( Exception ) {
            jv = JsonValue.CreateStringValue("");
            return "";
        }

        if ( jv.ValueType == JsonValueType.String ) {
            return jv.GetString();
        }
        else {
            return "";
        }
    }

    static string[] MyJsonArrayToStringArray(JsonArray a)
    {
        var s = new string[a.Count];
        for ( int i=0;i<a.Count;i++ ) {
            s[i] = a.GetStringAt((uint)i);
        }
        return s;
    }

    public string MakeStringOfJson()
    {
        return JsonValue.CreateStringValue(Name).Stringify() + ":" + (MakeJsonObject().Stringify());
    }

    JsonObject MakeJsonObject()
    {
        var jo = new JsonObject();

        JsonValue jvDescription = JsonValue.CreateStringValue(Description);
        jo.Add("description", jvDescription);

        if (_prefix.Length==1){
            JsonValue jvPrefix = JsonValue.CreateStringValue(_prefix[0]);
            jo.Add("prefix", jvPrefix); // Insertがない
        }
        else {
            var jaPrefix = new JsonArray();
            foreach ( string s in _prefix ) {
                jaPrefix.Add(JsonValue.CreateStringValue(s)); // Appendがない
            }
            jo.Add("prefix", jaPrefix);
        }

        var jaBody = new JsonArray();
        foreach ( string s in _body ) {
            jaBody.Add(JsonValue.CreateStringValue(s));
        }
        jo.Add("body", jaBody);

        return jo;
    }
}


class VscodeSnippetEditor : Form
{
    static string GetDefaultSnippetPath()
    {
#if DEBUG
        return (new FileInfo("csharp.json.txt")).FullName;
#else
        return Path.Combine(Environment.GetEnvironmentVariable("appdata"),
                             @"code\user\snippets\csharp.json");
#endif
    }

    TextBox txtName;
    TextBox txtPrefix;
    TextBox txtBody;
    TextBox txtDescription;
    SplitContainer spl;
    ListView lsv;

    readonly string Title = "Snippet editor";

    bool NotSavedYet{
        get{return _notSaved;}
        set{
            _notSaved = value;
            if(_notSaved){Text=Title+"*";}else{Text=Title;}
        }
    }
    bool _notSaved;

    string _head;
    string _tail;
    bool _txtChangedByProgram;

    readonly int ColumnIndex_Name   = 0;
    readonly int ColumnIndex_Prefix = 1;
    readonly int ColumnIndex_Lines  = 2;
    readonly int ColumnIndex_Body   = 3;
    readonly int ColumnIndex_Description = 4;

    VscodeSnippetEditor()
    {
        ClientSize = new Size(800, 600);
        Text=Title;

        spl = new SplitContainer();
        spl.Dock = DockStyle.Fill;
        spl.Orientation = Orientation.Horizontal;//Vertical;
        spl.SplitterDistance = 400;
        Controls.Add(spl);

        lsv = new ListView();
        lsv.View = View.Details;
        lsv.FullRowSelect = true;
        lsv.HideSelection = false;
        lsv.MultiSelect = false;
        lsv.GridLines = true;
        // 入れ替えなどでindexが変わる場合は ColumnIndex_xxxx もメンテすること
        lsv.Columns.Add("Name",        100, HorizontalAlignment.Left);
        lsv.Columns.Add("prefix",      100, HorizontalAlignment.Left);
        lsv.Columns.Add("lines",        50, HorizontalAlignment.Left);
        lsv.Columns.Add("body",        250, HorizontalAlignment.Left);
        lsv.Columns.Add("description", 300, HorizontalAlignment.Left);
        lsv.Dock = DockStyle.Fill;
        lsv.SelectedIndexChanged += Lsv_SelectedIndexChanged;
        spl.Panel1.Controls.Add(lsv);


        txtName = new TextBox();
        //txtName.Text = @"name";
        txtName.Location = new Point(0,0);
        txtName.Width = 200;
        txtName.TextChanged += TxtName_TextChanged;
        spl.Panel2.Controls.Add(txtName);

        txtPrefix = new TextBox();
        //txtPrefix.Text = @"prefix";
        txtPrefix.Location = new Point(0,30);
        txtPrefix.Width = 200;
        txtPrefix.TextChanged += TxtPrefix_TextChanged;
        spl.Panel2.Controls.Add(txtPrefix);

        txtDescription = new TextBox();
        //txtDescription.Text = @"description";
        txtDescription.Location = new Point(0,60);
        txtDescription.Width = 400;
        txtDescription.TextChanged += TxtDescription_TextChanged;
        spl.Panel2.Controls.Add(txtDescription);

        txtBody = new TextBox();
        txtBody.Multiline = true;
        txtBody.ScrollBars = ScrollBars.Both;
        //txtBody.Text = @"body";
        txtBody.Location = new Point(0,90);
        txtBody.Size = new Size(500, 300);
        txtBody.Font = new Font("MS ゴシック", txtBody.Font.Size, txtBody.Font.Unit);
        txtBody.TextChanged += TxtBody_TextChanged;
        spl.Panel2.Controls.Add(txtBody);

        Load += (s,e)=>{
            MyResize();
            LoadSnippetToListView(GetDefaultSnippetPath());
            ClearText();
        };

        Resize    += (s,e)=>{MyResize();};
        ResizeEnd += (s,e)=>{MyResize();};
        spl.SplitterMoving += (s,e)=>{MyResize();};
        spl.SplitterMoved  += (s,e)=>{MyResize();};

        FormClosing += Form_FormClosing;
    }

    void MyResize()
    {
        int w = spl.Panel2.ClientSize.Width;
        int h = spl.Panel2.ClientSize.Height - txtBody.Top;
        if(h<50){h=50;}
        txtBody.Size = new Size(w, h);
    }

    void Form_FormClosing(Object sender, FormClosingEventArgs e)
    {
        if ( !NotSavedYet ){return;} // nothing to do.

        DialogResult result = MessageBox.Show("File is not saved yet.\r\nOverwrite the snippet file?",
            "Confirmation",
            MessageBoxButtons.YesNoCancel,
            MessageBoxIcon.Exclamation,
            MessageBoxDefaultButton.Button3
        );

        if ( result == DialogResult.Yes ) {
            // overwrite the file
            if ( ! SaveSnippetFromListView(GetDefaultSnippetPath()) ) {
                MessageBox.Show("Failed to overwrite the snippet file.");
                e.Cancel = true;
            }
        }
        else if ( result == DialogResult.No ) {
            // nothing to do. form will be closed.
        }
        else if ( result == DialogResult.Cancel ) {
            e.Cancel = true;
        }
    }



    ListViewItem GetSelectedItemOfLsv()
    {
        var indices = lsv.SelectedIndices;
        if ( indices.Count != 1 ) { return null; }
        return lsv.Items[indices[0]];
    }


    void TxtName_TextChanged(object sender, EventArgs e)
    {
        if ( _txtChangedByProgram ) { return; }
        NotSavedYet = true;
        ListViewItem item = GetSelectedItemOfLsv();
        ((MyJsonItemInfo)item.Tag).Name = txtName.Text;
        item.SubItems[ColumnIndex_Name].Text = txtName.Text;
    }

    void TxtPrefix_TextChanged(object sender, EventArgs e)
    {
        if ( _txtChangedByProgram ) { return; }
        NotSavedYet = true;
        ListViewItem item = GetSelectedItemOfLsv();
        ((MyJsonItemInfo)item.Tag).Prefix = txtPrefix.Text;
        item.SubItems[ColumnIndex_Prefix].Text = ((MyJsonItemInfo)item.Tag).Prefix;
    }

    void TxtDescription_TextChanged(object sender, EventArgs e)
    {
        if ( _txtChangedByProgram ) { return; }
        NotSavedYet = true;
        ListViewItem item = GetSelectedItemOfLsv();
        ((MyJsonItemInfo)item.Tag).Description = txtDescription.Text;
        item.SubItems[ColumnIndex_Description].Text = ((MyJsonItemInfo)item.Tag).Description;
    }

    void TxtBody_TextChanged(object sender, EventArgs e)
    {
        if ( _txtChangedByProgram ) { return; }
        NotSavedYet = true;
        ListViewItem item = GetSelectedItemOfLsv();
        ((MyJsonItemInfo)item.Tag).Body = txtBody.Text;
        item.SubItems[ColumnIndex_Body].Text = ((MyJsonItemInfo)item.Tag).BodyFirstLine;
        item.SubItems[ColumnIndex_Lines].Text = ((MyJsonItemInfo)item.Tag).Lines.ToString();

    }

    void Lsv_SelectedIndexChanged(object sender, EventArgs e)
    {
        ListViewItem item = GetSelectedItemOfLsv();
        if(item==null){return;}

        _txtChangedByProgram = true;
        {
            txtName.Text = GetNameFromItem(item);
            txtPrefix.Text = GetPrefixFromItem(item);
            txtBody.Text = GetBodyFromItem(item);
            txtDescription.Text = GetDescciptionFromItem(item);
        }
        _txtChangedByProgram = false;
    }

    void ClearText()
    {
        _txtChangedByProgram = true;
        {
            txtName.Text = "(name)";
            txtPrefix.Text = "(prefix)";
            txtBody.Text = "(body)";
            txtDescription.Text = "(description)";
        }
        _txtChangedByProgram = false;
    }

    bool SaveSnippetFromListView(string path)
    {
        StreamWriter writer = new StreamWriter(path, false);
        try {
            writer.Write(_head);
            writer.Write("\n");

            for ( int i=0 ; i<lsv.Items.Count ; i++ ) {
                ListViewItem item = lsv.Items[i];
                var t = (MyJsonItemInfo)item.Tag;
                writer.Write(t.MakeStringOfJson());
                if (i==lsv.Items.Count-1){
                    writer.Write("\n");
                }
                else{
                    writer.Write(",\n");
                }
            }
            writer.Write(_tail);
        }
        catch ( IOException e ) {
            MessageBox.Show(e.ToString());
            return false;
        }
        finally{
            writer.Close();
        }

        NotSavedYet = false;
        return true;
    }

    // return body
    string ReadAndSeparateSnippetText(string s, out string head, out string tail)
    {
        head = null;
        tail = null;

        Regex rBegin = new Regex(@"^[ \t]*//[ \t]*%%kob58im-ToolBegin%%[ \t]*\r?$", RegexOptions.Multiline);
        Regex rEnd   = new Regex(@"^[ \t]*//[ \t]*%%kob58im-ToolEnd%%[ \t]*\r?$", RegexOptions.Multiline);

        Match mBegin = rBegin.Match(s);
        if ( !mBegin.Success ) {
            MessageBox.Show("Cannot find start mark\r\n// %%kob58im-ToolBegin%%");
            return null;
        }
        Match mEnd   = rEnd.Match(s);
        if ( !mEnd.Success ) {
            MessageBox.Show("Cannot find end mark\r\n// %%kob58im-ToolEnd%%");
            return null;
        }

        int posEndOfBeginMark = mBegin.Groups[0].Captures[0].Index
                               +mBegin.Groups[0].Captures[0].Length;
        int posStartOfEndMark = mEnd.Groups[0].Captures[0].Index;

        head = s.Substring(0, posEndOfBeginMark);
        tail = s.Substring(posStartOfEndMark);
        return s.Substring(posEndOfBeginMark, posStartOfEndMark - posEndOfBeginMark);
    }

    bool LoadSnippetToListView(string path)
    {
        if (!File.Exists(path)) {
            MessageBox.Show("Cannot find file: " + path);
            return false;
        }

        string s;
        try {
            s = File.ReadAllText(path);
        }
        catch ( IOException e ) {
            MessageBox.Show(e.ToString());
            return false;
        }
        string editablePart = ReadAndSeparateSnippetText(s, out _head, out _tail);

        JsonObject joRoot = JsonObject.Parse("{"+editablePart+"}");

        lsv.BeginUpdate();
        try {
            foreach(string name in joRoot.Keys)
            {
                JsonValue jvItem = joRoot.GetNamedValue(name);

                if (jvItem.ValueType == JsonValueType.Object) {
                    JsonObject joItem = jvItem.GetObject();
                    ListViewItem item = MakeItemFromJson(name, joItem);
                    lsv.Items.Add(item);
                }
            }
        }
        finally {
            lsv.EndUpdate();
        }

        return true;
    }

    ListViewItem MakeItemFromJson(string keyName, JsonObject jo)
    {
        var t = new MyJsonItemInfo(keyName, jo);

        string prefix = t.Prefix;
        string[] body = t.BodyAsArray;
        string description = t.Description;

        string firstLine="";
        if (body.Length>=1){firstLine=body[0];}
        var item = new ListViewItem(new string[]{keyName, prefix, body.Length.ToString(), firstLine, description});
        item.Tag = t;
        return item;
    }

    string GetNameFromItem(ListViewItem item) {        return ((MyJsonItemInfo)item.Tag).Name; }
    string GetPrefixFromItem(ListViewItem item) {      return ((MyJsonItemInfo)item.Tag).Prefix; }
    string GetBodyFromItem(ListViewItem item) {        return ((MyJsonItemInfo)item.Tag).Body; }
    string GetDescciptionFromItem(ListViewItem item) { return ((MyJsonItemInfo)item.Tag).Description; }


    [STAThread]
    static void Main(string[] args)
    {
        Application.Run(new VscodeSnippetEditor());
    }
}

コンパイル方法

cscのパスは通っている前提。

バッチファイル

csc /r:C:\Windows\Microsoft.NET\assembly\GAC_MSIL\System.Runtime.WindowsRuntime\v4.0_4.0.0.0__b77a5c561934e089\system.runtime.windowsruntime.dll ^
/r:C:\Windows\Microsoft.NET\assembly\GAC_MSIL\System.Runtime.InteropServices.WindowsRuntime\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Runtime.InteropServices.WindowsRuntime.dll ^
/r:C:\Windows\Microsoft.NET\assembly\GAC_MSIL\System.Runtime\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Runtime.dll ^
"/r:C:\Program Files (x86)\Windows Kits\8.1\References\CommonConfiguration\Neutral\Annotated\Windows.winmd" %*

入力データ

csharp.json.txt

{
    // Place your snippets for csharp here. Each snippet is defined under a snippet name and has a prefix, body and 
    // description. The prefix is what is used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
    // $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. Placeholders with the 
    // same ids are connected.
    // Example:
    "click event":  { "prefix": "Click",  "body": ["Click  += (sender,e)=>{;};"], "description": "Event lambda expression" },
    "load event":   { "prefix": "Load",   "body": ["Load   += (sender,e)=>{;};"], "description": "Event lambda expression" },
    "resize event": { "prefix": "Resize", "body": ["Resize += (sender,e)=>{;};"], "description": "Event lambda expression" },
    // %%kob58im-ToolBegin%%
    "label constructor": {
        "prefix": "label",
        "body": [
            "$1 lbl = new Label();",
            "lbl.Text = @\"\";",
            "lbl.TextAlign = ContentAlignment.MiddleLeft;",
            "lbl.Location = new Point(,);",
            "lbl.Size = new Size(,);",
            "Controls.Add(lbl);"
        ],
        "description": "label constructor"
    },
    "textbox constructor": {
        "prefix": "textbox",
        "body": [
            "$1 txt = new TextBox();",
            "txt.Text = @\"\";",
            "txt.Location = new Point(,);",
            "txt.Width = ;",
            "Controls.Add(txt);"
        ],
        "description": "textbox constructor"
    },
    "multiline textbox constructor": {
        "prefix": "textboxm",
        "body": [
            "$1 txt = new TextBox();",
            "txt.Multiline = true;",
            "txt.ScrollBars = ScrollBars.Both;",
            "txt.Text = @\"\";",
            "txt.Location = new Point(,);",
            "txt.Size = new Size(,);",
            "Controls.Add(txt);"
        ],
        "description": "multiline textbox constructor"
    },
    "listview constructor": {
        "prefix": ["listview","hoge"],
        "body": [
            "$1 lsv = new ListView();",
            "lsv.View = View.Details;",
            "lsv.FullRowSelect = true;",
            "lsv.HideSelection = false;",
            "lsv.MultiSelect = false;",
            "lsv.GridLines = true;",
            "lsv.Columns.Add(\"Item\",  100, HorizontalAlignment.Left);",
            "lsv.Columns.Add(\"Value\", 400, HorizontalAlignment.Left);",
            "lsv.Location = new Point(,);",
            "lsv.Size = new Size(,);",
            "//lsv.Dock = DockStyle.Fill;",
            "Controls.Add(lsv);"
        ],
        "description": "listview constructor"
    },
    "task": {
        "prefix": "Task",
        "body": [
            "Task.Run(() => {",
            "",
            "    // this.Invoke((MethodInvoker)(() => xxx.yyy = zzz)); // UI control",
            "",
            "});"
        ],
        "description": "Task"
    },
    "using directive": {
        "prefix": "using",
        "body": [
            "using "
        ],
        "description": "using directive"
    },
    "CSharp Template": {
        "prefix": "usingall",
        "body": [
            "using System;",
            "using System.Collections;",
            "using System.Collections.Generic;",
            "using System.ComponentModel;",
            "using System.Data;",
            "using System.Diagnostics;",
            "using System.Drawing;",
            "using System.Drawing.Drawing2D;",
            "using System.Drawing.Imaging;",
            "using System.IO;",
            "using System.Reflection;",
            "using System.Runtime.InteropServices;",
            "using System.Text;",
            "using System.Text.RegularExpressions;",
            "using System.Threading;",
            "using System.Threading.Tasks;",
            "using System.Windows.Forms;",
            "",
            "class XXXX : Form",
            "{",
            "    XXXX()",
            "    {",
            "        //Text = ;",
            "        //ClientSize = new Size(,);",
            "        //Controls.Add(xx);",
            "    }",
            "    ",
            "    [STAThread]",
            "    static void Main(string[] args)",
            "    {",
            "        Application.Run(new XXXX());",
            "    }",
            "}",
            ""
        ],
        "description": "Insert the template code"
    },
    "CSharp Template2": {
        "prefix": "usingconsole",
        "body": [
            "using System;",
            "using System.Collections;",
            "using System.Collections.Generic;",
            "using System.IO;",
            "using System.Text;",
            "using System.Text.RegularExpressions;",
            "",
            "class XXXX",
            "{",
            "    [STAThread]",
            "    static void Main(string[] args)",
            "    {",
            "        ;",
            "    }",
            "}",
            ""
        ],
         "description": "Insert the template code"
    }
    // %%kob58im-ToolEnd%%
}

  1. //コメントとか 

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0