みかづきメモ

学習したことのメモとか、日記とか、備忘録。

Visual Studio の拡張機能を作成する - カスタムプロジェクト編

前回、シンタックスハイライトまで作成しました。
今回は、カスタムプロジェクトを作成してみようと思います。


Managed Package Framework の追加

カスタムプロジェクトってなんぞやって話ですが、 C# のプロジェクトの場合は *.csproj が、
Visual Basic の場合は *.vbproj といったプロジェクト管理ファイルが生成されます。

このように、作成した言語サービスを対象とした .*proj を追加する感じですね。

では、まずはじめに、 Python Tools for Visual Studio を適当な場所にクローンします。
GitHub - Microsoft/PTVS: Python Tools for Visual Studio

次に、 PTVS_ROOT\Common\Product\SharedProject と進み、ファイルをすべてコピーします。

ちなみに、公式では Managed Package Framework for Projects を使用しています。
MPF for Projects - Visual Studio 2013 - Home
こっちは MS-PL であるため、 Apache である PTVS よりは使いにくいのかもしれません。
※なお、VS2013 となっていますが、 VS2015 でも使用できます。

追加したら、 References として、以下のものを追加します。


テンプレートの作成

次は、「新しく作成する」などに表示される、テンプレートを作ります。
適当なフォルダー(ここでは ProjectTemplates )に作っていきます。

中身はこんな感じ。

ProjectTemplates
|- ConsoleApplication
   |- ConsoleApplication.hsproj
   |- Program.hsp
   |- ConsoleApplication.vstemplate
   |- ConsoleApplication.ico

ConsoleApplication というディレクトリを作成し、それぞれのファイルを作成します。
*.ico.hsp はそれぞれ自由なアイコンとプロジェクトに含めるファイルです。
*.hsproj はプロジェクト管理ファイルです。
C# だと *.csproj に該当するファイルです。
*.csproj と同じように、プロジェクト構成を記述していきます。
上記プロジェクトの場合だと、下のような構成になるはずです。

<?xml version="1.0" encoding="utf-8" ?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <SchemaVersion>2.0</SchemaVersion>
    <ProjectGuid></ProjectGuid>
    <OutputType>Exe</OutputType>
    <AssemblyName>ConsoleApplication</AssemblyName>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
    <DebugSymbols>true</DebugSymbols>
    <OutputPath>bin\Debug\</OutputPath>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
    <DebugSymbols>false</DebugSymbols>
    <OutputPath>bin\Release\</OutputPath>
  </PropertyGroup>
  <ItemGroup>
    <Compile Include="Program.hsp">
      <SubType>Code</SubType>
    </Compile>
  </ItemGroup>
</Project>

最後は *.vstemplate ですが、正直これはなくても読み込ませれるのですが、何らかの処理をさせる倍は
作成しておく必要があります。

*.vstemplate の中身も XML なので、 *.hsproj 同様書いていきます。

<?xml version="1.0" encoding="utf-8"?>
<VSTemplate Version="3.0.0" Type="Project" xmlns="http://schemas.microsoft.com/developer/vstemplate/2005">
  <TemplateData>
    <Name>Console Application</Name>
    <Description>A project for creating a new HSP3 Windows console application.</Description>
    <Icon>ConsoleApplication.png</Icon>
    <ProjectType>HSP</ProjectType>
    <SortOrder>20</SortOrder>
    <CreateNewFolder>true</CreateNewFolder>
    <DefaultName>ConsoleApplication</DefaultName>
    <ProvideDefaultName>true</ProvideDefaultName>
    <CreateInPlace>true</CreateInPlace>
    <PromptForSaveOnCreation>true</PromptForSaveOnCreation>
  </TemplateData>
  <TemplateContent>
    <Project TargetFileName="$safeprojectname$.hsproj" File="ConsoleApplication.hsproj" ReplaceParameters="true">
      <ProjectItem OpenInEditor="true">Program.hsp</ProjectItem>
    </Project>
  </TemplateContent>
</VSTemplate>

<TemplateData> の子要素では、プロジェクト作成ダイアログに用いられている名前や説明、カテゴリーなどを、
<TemplateContent> の子要素では、プロジェクトのアイテムと、その処理を記述しています。
それぞれの子要素についての詳しい解説は、 MSDN を参照してください。

テンプレートに含めるすべてのアイテムを作成し終えたら、アイテムをすべて選択し、
ビルドアクションを ZipProject に設定します。
これでテンプレートの追加が完了しました。
なお、テンプレートの適用には、 VS ExpInstance のリセットを行う必要があります。


カスタムプロジェクトの登録~追加

前のセクションで *.hsproj なテンプレートを追加しましたが、この状態では、
Visual Studio で扱うことができません。

Visual Studio で扱えるようにするためには、プロジェクトタイプを登録する必要があります。
プロジェクトタイプの登録には、別で Package を提供します。

%ROOT_NAMESPACE%.ProjectHSPProjectPackage クラスを追加し、以下のように記述します。

using System.ComponentModel.Composition;
using System.Runtime.InteropServices;

using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudioTools.Project;

namespace HSPToolsVS.Project
{
    [PackageRegistration(UseManagedResourcesOnly = true)]
    [Guid(HSPToolsConstants.ProjectPackageGuid)]
    // Register project templates.
    [ProvideProjectFactory(typeof(HSPProjectFactory), null,
        "HSP Project File(*.hsproj);*.hsproj", "hsproj", "hsproj", 
        @".\NullPath", LanguageVsTemplate = "HSP")]
    [Export]
    public sealed class HSPProjectPackage : CommonProjectPackage
    {
        #region Overrides of CommonProjectPackage

        public override ProjectFactory CreateProjectFactory() => new HSPProjectFactory(this);

        public override CommonEditorFactory CreateEditorFactory() => null; // Do not need.

        public override uint GetIconIdForAboutBox() => 0; // IconId is not defined.

        public override uint GetIconIdForSplashScreen() => 0; // IconID is not defined;

        public override string GetProductName() => "HSP";

        public override string GetProductDescription() => "HSP";

        public override string GetProductVersion() => GetType().Assembly.GetName().Version.ToString();

        #endregion
    }
}

ProvideProjectFactoryAttribute にて、先ほど作成した *.hsproj を扱えるように設定しています。
また、第2引数には言語名を指定できるのですが、 null を設定しておきます。
テンプレートパスには .\NullPath を設定することにより、 ZipProjectProjectTemplates に展開してくれます。
なお、この時 LanguageVsTemplate には、 *.vstemplate にて設定した ProjectType
同様の値を設定する必要があります。

次に HSPProjectFactory を作成します。

using System;
using System.Runtime.InteropServices;

using Microsoft.VisualStudioTools.Project;

using IOleServiceProvider = Microsoft.VisualStudio.OLE.Interop.IServiceProvider;

namespace HSPToolsVS.Project
{
    [Guid(HSPToolsConstants.ProjectFactoryGuid)]
    internal class HSPProjectFactory : ProjectFactory
    {
        private readonly CommonProjectPackage _package;

        public HSPProjectFactory(CommonProjectPackage package) : base((IServiceProvider) package)
        {
            _package = package;
        }

        #region Overrides of ProjectFactory

        internal override ProjectNode CreateProject()
        {
            var stream = GetType().Assembly.GetManifestResourceStream("HSPToolsVS.Project.Resources.imagelis.bmp");
            var project = new HSPProjectNode(Site, Utilities.GetImageList(stream));
            var package = ((IServiceProvider) _package).GetService(typeof(IOleServiceProvider));
            project.SetSite((IOleServiceProvider) package);
            return project;
        }

        #endregion
    }
}

普通に実装していきます。
注意点としては、 imagelis.bmp埋め込みリソース としておいてください。

次は HSPProjectNode です。

using System;
using System.Drawing;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows.Forms;

using Microsoft.VisualStudioTools.Project;

namespace HSPToolsVS.Project
{
    [Guid(HSPToolsConstants.ProjectNodeGuid)]
    internal class HSPProjectNode : CommonProjectNode
    {
        public HSPProjectNode(IServiceProvider serviceProvider, ImageList imageList) : base(serviceProvider, imageList)
        {
        }

        #region Overrides of ProjectNode

        internal override string IssueTrackerUrl => "https://github.com/fuyuno/HSPToolsVS/issues";

        protected sealed override Stream ProjectIconsImageStripStream
        {
            get { throw new NotSupportedException("HSP Tools does not support strip icons"); }
        }

        #endregion

        #region Overrides of CommonProjectNode

        public override Type GetProjectFactoryType() => typeof(HSPProjectFactory);

        public override Type GetEditorFactoryType() => null;

        public override string GetProjectName() => "HSP";

        public override string GetFormatList() => "HSP Script Files(*.hsp,*.as)|*.hsp;*.as";

        public override Type GetGeneralPropertyPageType() => null;

        public override Type GetLibraryManagerType() => null;

        public override IProjectLauncher GetLauncher() => null;

        #endregion
    }
}

プロジェクトのアイテムにアイコンを設定する場合は、別途ソースを記述する必要がありますが、
今回は必要が無いので実装しません。
そのうちやります。

ここまでできたところで、 VS ExpInstance を起動すれば、新しいプロジェクトが追加され、
また Visual Studio で扱うことができるようになっていることが確認できると思います。


参考