LINQPadのhtml言語向けがほしかったので作ってみた
この話はほとんどC#で作ったデスクトップアプリの話になります。
目次
- 目次
- プログラミング言語の勉強するときって・・・
- 探したけど見つけられなかった・・・
- 作ったろうやないかい!
- あらかじめ1-3まで用意したものを準備しておきます
- 仕様
- あらかじめ完成したものがこちらの画像です
- 結論
プログラミング言語の勉強するときって・・・
例えば自分の場合、教科書でもサイトでも、ブラウザを最大化表示しつつ、エディタやIDEを小さいサイズに調整して、コード打ち込んで実行して「あ~こんな感じで動くのね。」と動かしながら学んでいくのですが、いちいち新しいソースファイルを作成して保存して実行して、また新しいソースファイルを作成して保存して実行して、、、とやっていくと、どんどんソースファイルが増えていきます。
これがちょっとアレだよね~と思っていました。分かってしまった後、これらはごみファイルでしかありません(悲しい扱い)。言語を学びたいのであって、ソースファイルを作りたいわけではないんですよね(勉強したいときは)。
で、.NET の場合はLINQPadという神様が考えられたアプリがあります。いちいちソースファイルに保存しなくても実行できる!変数をビジュアライズで見れるので構造が理解しやすい!というものです。
唐突にhtml, css, JavaScriptを勉強したかった自分としては、前半のファイル保存せずに実行するアプリが欲しかったのでした(とりあえずはフロントエンドさえ動かせればOKなノリ)。データのビジュアライズはあれば嬉しいけど、JavaScriptにリフレクション的なのってあるのかな?まぁ分からないので無くてもヨシ!でした。
で、普通に勉強しようとするとhtml, css, JavaScript用に3つファイルを作成してメモ帳や任意のエディタで開いて、ブラウザを開いて、htmlファイルを読み込ませないといけません。4アプリが必要になるのです。
これを、1アプリ内で完結させたい、ソースファイルは保存したくない、というイヤイヤ期になりました(赤ちゃんか!)。
探したけど見つけられなかった・・・
なんかないかなぁとネットを探してみましたが探せませんでしたorz。
作ったろうやないかい!
見つかるまで探すマンにはなれず、もう作った方が速くねマンだったので(面倒くさかっただけかも)作りました。ここからは3分クッキングアプリです。
- C#/WPFプロジェクトを作成します(確か.NETFramework は4.5以上の方が良かったような?)。
- アーキテクチャをx86に切り替えます(AvalonDock, CefSharp を動かすため)
- NuGetで以下を取ってきます。
- AvalonDock
- AvalonEdit
- CefSharp(Chromiumブラウザを使ってプレビューさせる)
- xaml, コードビハインドを書いて、はい出来上がり!
あらかじめ1-3まで用意したものを準備しておきます
html, css, JavaScriptのシンタックスハイライトをしたかったためAvalonEdit, 配置を好き勝手に変えたくなると思ったのでAvalonDock, IEではなくChrome 系が良いなと思ったのでCefSharpです。AvalonEditはインテリセンス補完も欲しかったのですが、調査が必要っぽかったのであきらめました(強力機能よりも最速のシンプル完成を優先しました)。
ではここに準備しておいたソリューションに対して、xamlとコードビハインドをコピペしましょう!以下のソースでは、プロジェクト名をJSPad
とか格好つけていますが、任意の名前空間に合わせて変えてください。
htmlの勉強を早くしたかったし、完成できればいいやと思っていたので、MVVMではなくコードビハインドでちゃちゃっと作っています。イベントの購読はxaml上ではなく、コントロールに名前を付けておいてコードビハインドで購読しています。
MainWindow.xaml
<Window x:Class="JSPad.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:JSPad" xmlns:dock="http://schemas.xceed.com/wpf/xaml/avalondock" xmlns:edit="http://icsharpcode.net/sharpdevelop/avalonedit" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <dock:DockingManager> <dock:LayoutRoot> <dock:LayoutPanel Orientation="Vertical"> <!-- HTML, CSS, JavaScript ペイン --> <dock:LayoutDocumentPane> <dock:LayoutDocument Title="HTML"> <edit:TextEditor x:Name="htmlEditor" FontFamily="Consolas" FontSize="16" SyntaxHighlighting="HTML" ShowLineNumbers="True" /> </dock:LayoutDocument> <dock:LayoutDocument Title="CSS"> <edit:TextEditor x:Name="cssEditor" FontFamily="Consolas" FontSize="16" SyntaxHighlighting="CSS" ShowLineNumbers="True" /> </dock:LayoutDocument> <dock:LayoutDocument Title="JavaScript"> <DockPanel> <ToolBarTray DockPanel.Dock="Top"> <ToolBar> <Button x:Name="devToolsButton" Content="DevTools(別画面)を表示" /> </ToolBar> </ToolBarTray> <edit:TextEditor x:Name="jsEditor" FontFamily="Consolas" FontSize="16" SyntaxHighlighting="JavaScript" ShowLineNumbers="True" /> </DockPanel> </dock:LayoutDocument> </dock:LayoutDocumentPane> <!-- ブラウザプレビュー(ChromiumWebBrowser はコードビハインド上から登録、xaml 上だとデザインが無効になってしまう) --> <dock:LayoutAnchorablePane DockHeight="200"> <dock:LayoutAnchorable Title="プレビュー"> <ContentControl x:Name="browserContainer" /> </dock:LayoutAnchorable> </dock:LayoutAnchorablePane> </dock:LayoutPanel> </dock:LayoutRoot> </dock:DockingManager> </Window>
MainWindow.xaml.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; //using System.Windows.Shapes; // Path.xxx の名前衝突のため using System.IO; using CefSharp.Wpf; using CefSharp; // AvalonDock, CefSharp のためにも x86 アーキテクチャがいいかも namespace JSPad { /// <summary> /// MainWindow.xaml の相互作用ロジック /// </summary> public partial class MainWindow : Window { ChromiumWebBrowser browser = null; public MainWindow() { InitializeComponent(); browser = new ChromiumWebBrowser(); browserContainer.Content = browser; htmlEditor.TextChanged += Editor_TextChanged; cssEditor.TextChanged += Editor_TextChanged; jsEditor.TextChanged += Editor_TextChanged; devToolsButton.Click += (s, e) => { if (browser.IsBrowserInitialized) browser.ShowDevTools(); }; htmlEditor.Text = @"<!DOCTYPE html> <html lang='ja'> <head> <meta charset='utf-8'> <title>Test Page</title> </head> <body> <div id='test'>Hello World!</div> </body> </html> "; } // 3つのエディタ内容を1つのhtmlソースにマージして表示します。 private void Editor_TextChanged(object sender, EventArgs e) { // html に、css と javascript を合体させる var html = htmlEditor.Text; var css = cssEditor.Text; var js = jsEditor.Text; // html if (!string.IsNullOrWhiteSpace(html)) { // css if (!string.IsNullOrWhiteSpace(css)) { css = $"<style type='text/css'>{css}</style>"; html = html.Replace("</head>", $"{css}</head>"); } // javascript if (!string.IsNullOrWhiteSpace(js)) { js = $"<script type='text/javascript'>{js}</script>"; html = html.Replace("</body>", $"{js}</body>"); } } else { // html, css が空欄で、javascript がある場合、javascriptが動くように最低限のhtmlを用意して表示させる if (string.IsNullOrWhiteSpace(css) && !string.IsNullOrWhiteSpace(js)) { html = @"<!DOCTYPE html> <html lang='ja'> <head> <meta charset='utf-8'> <title>Test Page</title> </head> <body> </body> </html> "; js = $"<script type='text/javascript'>{js}</script>"; html = html.Replace("</body>", $"{js}</body>"); } } // 作業フォルダの作成とhtmlファイルの保存 var appDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "work"); if (Directory.Exists(appDirectory)) Directory.Delete(appDirectory, true); Directory.CreateDirectory(appDirectory); var htmlFile = Path.Combine(appDirectory, "index.html"); File.WriteAllText(htmlFile, html); // ブラウザに表示 browser.Address = htmlFile; } } }
仕様
仕組みとしては、以下のようにcss, javascriptをhtmlにマージさせて、実行ファイルと同じ場所にhtmlソースファイルを作成してブラウザに読み込ませています。
- cssの内容をstyleタグで囲って、headの閉じタグ直前に挿入
- JavaScriptの内容をscriptタグで囲って、bodyの閉じタグ直前に挿入
- 1つのhtmlファイルとしてファイル保存&ブラウザ読み込み
ただし、html, cssが空欄で、JavaScriptのみ空欄ではない場合でも動作させたかったので、この場合は、最低限のhtmlを自動生成して組み込んでいます。
あらかじめ完成したものがこちらの画像です
Hello World! に対して、css で文字の色付けが適用されて、その後JavaScriptで表示文字の書き換えが行われました。
console.log("xx")
などの場合は、DevTools画面を表示させて確認します。
JavaScriptのみの場合でも確認できます。
結論
全体的にホワイトなので明るすぎるのでダークモードも組み込みたいし、インテリセンスも組み込みたいし、個別保存も対応したいし、TextChanged イベントでやりくりするのはちょっと厳しいかもね~とか思いながらも、後でいいやと思ってしまっていたり。