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として扱い、読み込みます。(複数ペアは不可)
画面キャプチャ
やってた最中にはまったこと
・VSCodeのJsonは改行コードが\n
のよう
・C#のRegex
のRegexOptions.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%%
}
-
//
コメントとか ↩