2016/01/06

Trim Copyリリース

Visual Studio拡張機能のTrim Copyをリリースしました。
一言でいうと、コードブロック単位のTrimStart(行頭の空白削除)付きのコピーです。以下の二種類のコピーができます。
  • Copy (no indent)
  • Copy (fixed indent)
Copy (no indent)では行頭の空白(スペース、タブ等)を全削除します。Copy (fixed indent)では行頭の空白を固定幅に調整します(元の幅が大きければ削除し、小さければ挿入する)。行頭のタブはスペースに置換します。

これの何が嬉しいかというと、Visual Studioで書いたコードをMarkdown等にコピーする場面がありますが、このときに行頭の余分な空白は無駄だし、見映えも悪いしで削除したりします。まず名前空間から始まって、クラス、メソッド、メソッド中のブロックとインデントが深くなるにつれ、その一部を切り出したいときにインデント削除が地味に面倒だったりします。標準のMarkdownだとコードブロック指定は4スペース空けですが、これに合わせて調整するのも同じく。

そこは一括置換するなり色々工夫されていると思いますが、そもそもVisual Studioからコピーした時点で処理されてればそういう手間も要らないよね、というのがこの拡張機能です。拡張機能ならではの特長として、自分で選択範囲を行頭や行末にきっちり合わせなくても、自動的に開始行の行頭から終了行の行末までに選択し直してからコピーするので、変にずれたり切れたりすることがありません。

個人的にVisual Studioのコード補完や整形の効いた快適な環境でコードを書いた後の、MarkdownやStackOverflow用の整形が割と億劫だったのですが、これを使えば一発ですし、後から一部修正しても全部コピーし直せばいいので楽です。

具体例を挙げると、以下は拡張機能からユーザーのタブサイズ設定を取得するクラスです。
using System;
using Microsoft.VisualStudio.Settings;
using Microsoft.VisualStudio.Shell.Settings;
namespace TrimCopy.Models
{
internal class UserSettings
{
private const string TabSizeName = "Tab Size";
public static int? GetTabSize(IServiceProvider serviceProvider, string languageName)
{
return GetValueFromTextEditor(serviceProvider, languageName, TabSizeName);
}
private static int? GetValueFromTextEditor(IServiceProvider serviceProvider, string languageName, string propertyName)
{
if (serviceProvider == null) return null;
if (string.IsNullOrWhiteSpace(languageName)) return null;
SettingsManager settingsManager = new ShellSettingsManager(serviceProvider);
var userSettingsStore = settingsManager.GetReadOnlySettingsStore(SettingsScope.UserSettings);
var textEditorLanguage = $@"Text Editor\{languageName}";
if (userSettingsStore.CollectionExists(textEditorLanguage) &&
userSettingsStore.PropertyExists(textEditorLanguage, propertyName))
{
return userSettingsStore.GetInt32(textEditorLanguage, propertyName);
}
return null;
}
}
}
view raw UserSettings.cs hosted with ❤ by GitHub
この中のGetValueFromTextEditorメソッドをCopy (no indent)したものがこれ。
private static int? GetValueFromTextEditor(IServiceProvider serviceProvider, string languageName, string propertyName)
{
if (serviceProvider == null) return null;
if (string.IsNullOrWhiteSpace(languageName)) return null;
SettingsManager settingsManager = new ShellSettingsManager(serviceProvider);
var userSettingsStore = settingsManager.GetReadOnlySettingsStore(SettingsScope.UserSettings);
var textEditorLanguage = $@"Text Editor\{languageName}";
if (userSettingsStore.CollectionExists(textEditorLanguage) &&
userSettingsStore.PropertyExists(textEditorLanguage, propertyName))
{
return userSettingsStore.GetInt32(textEditorLanguage, propertyName);
}
return null;
}
全然たいしたことはしてませんが、こういうものが一発で出るとMarkdown書きの面倒さが緩和されます。

もしMarkdown等へのコピーでフラストレーションを感じていれば試してみてください。

2016/01/01

UniRxでUnityWebRequest

UnityのWWWに代わる新しいUnityWebRequestが5.3で実プラットフォームで使えるようになったので、いつもの如くRxにしようとしたら、まだUniRxにラップしたクラスがなかったので、勉強がてら作ってみました。
といっても、neueccさんご本人がIssueを立てられているので、公式でもすぐサポートされると思いますが、そこはそれ。

流れとしては、UniRxにはCoroutineをObservableに変換するObservable.FromCoroutineがあるので、UnityWebRequestオブジェクトをいい感じにCoroutineにして、これをObservableに変換します。というか、ObservableWWWをベースにUnityWebRequestに合わせて修正したものを、UnityWebRequestのメソッドと引数に合わせてラップしていくだけです。

UnityWebRequestの基本的なCoroutineはこんな感じです。
using System.Collections;
using UnityEngine;
using UnityEngine.Experimental.Networking;
public class UnityWebRequestUsage : MonoBehaviour
{
void Start()
{
StartCoroutine(GetText());
}
IEnumerator GetText()
{
using (UnityWebRequest request = UnityWebRequest.Get("http://unity3d.com/"))
{
yield return request.Send();
if (request.isError) // Error
{
Debug.Log(request.error);
}
else // Success
{
Debug.Log(request.downloadHandler.text);
}
}
}
}
UnityWebRequest.Getはファクトリーメソッドで、これにurlほかの引数を与えるとUnityWebRequestオブジェクトが返ってくるので、Sendメソッドを実行してyield returnで待ち、結果をisErrorプロパティで判別して、成功ならdownloadHandlerプロパティにアタッチされているDownloadHandlerへの参照が入っているので(ファクトリーメソッドによる)、ここからダウンロードされたものを取得します。

ファクトリーメソッドはGetのほか、Post、Put、Delete、HeadでRESTをカバーしていて、基本のGet以外にTexture用のGetTextureとAssetBundle用のGetAssetBundleもあります。実行はすべてSendで行われ、WWWのようにコンストラクト即実行とは違います。これに引っ掛かって少し悩みました。

これと同じことを、作成したObservableWebRequestでやるとこんな感じです。
using UnityEngine;
using UniRx;
public class ObservableWebRequestUsage : MonoBehaviour
{
void Start()
{
ObservableWebRequest.Get("http://unity3d.com/")
.Subscribe(
x => Debug.Log(x), // Success
ex => Debug.LogException(ex)); // Error
}
}
ObservableWWWとクラス名が違うだけでした。

基本的なテストは基本のGetで確認しましたが、他はこれからです。といっても、UnityWebRequestは標準で使う限りシンプルなので、間違える要素は少ないと思いますが。