last updated:2024/08/05

Hidemaru Q and A

第III部〜秀丸マクロのいろはにほへと
 JavaScript対応


JavaScript対応

はじめに

Ver.9.19以降、マクロファイル内にある js{ } 記述内は JavaScript で記述することが可能になりました。 execjs文で別ファイルの JavaScript も実行できます。

JavaScript 実行時には、HidemaruGlobal オブジェクト、Hidemaru オブジェクト が常にグローバルに存在します。秀丸マクロ命令の実行や秀丸エディタの機能を使うには、この2つのオブジェクトを利用します。

  • HidemaruGlobalオブジェクトは、秀丸マクロの文/キーワード/関数と同名のメソッドがあります。 jsmode文により、グローバル展開をON/OFFできる。
  • Hidemaruオブジェクトは、秀丸エディタ本体関連機能がメソッドとして呼び出せます。 JavaScript と 秀丸マクロ 間で変数をやり取りする場合は、Hidemaruオブジェクトを使います。

他にも使用できるオブジェクトがあるのでマクロヘルプの JavaScript対応オブジェクト を参照してください。

JavaScriptでなにができるのか

JavaScript は汎用プログラム言語です。各種ライブラリも利用できます。

JavaScript の使い方としては以下の方法があります。どちらの方法がいいかはマクロによるのでいろいろ試してみましょう。

JavaScript実行環境

JavaScriptの実行環境(JavaScriptエンジン)としては以下の3つを指定可能です。

jsmode文のパラメータ1で文字列として指定します。明示的に指定しない場合は、"JScript" が使用されます。

  • JScript(デフォルト)

    WSHベースのエンジンで秀丸エディタを実行できる環境なら標準搭載されています。

    対応規格は古い(ECMA-262 3rd edition(December 1999))。

    JScriptの資料はJScript and VBScript Microsoft Learnを参照。

  • Chakra

    IEベースのエンジン。使用できる環境と出来ない環境がある。

    対応規格は古い(ECMA-262 5.1 edition(December 2009))。

  • WebView2

    Chromium版 Microsoft Edge相当のエンジン。

    Windows10、11以外の場合は、MicrosoftのサイトからWebView2ランタイムをインストールする必要があります。 (Windows 7 / 8 / 8.1 をサポートしているのは、WebView2ランタイム バージョン 109 が最終になります)

    JavaScriptの資料はJavaScript MDNを参照。

新しい JavaScript を使うには「WebView2」を指定しますが、実行環境としては Windows 10 / 11 が主なターゲットになります。どの Windows 環境でも動かしたい場合は「JScript」を使うことになります。

JavaScriptから秀丸マクロの文/キーワード/関数の呼び出し方法

秀丸マクロの文/キーワード/関数は HidemaruGlobal オブジェクトのメソッドとして呼び出すことができます。

  • 一部例外はあるが 文/キーワード/関数 をそのままのメソッドとして使用できる。
    詳細は HidemaruGlobalオブジェクトのメソッドを参照。
  • メソッドとして呼び出す為、全て関数の書き方(メソッドの書き方)になる。(要は括弧を付けて呼び出す)
マクロの例
//filename2キーワードとmessage文の呼び出し
js {
    //HidemaruGlobalオブジェクト の名前は「hidemaruGlobal」
    a = hidemaruGlobal.filename2();
    hidemaruGlobal.message(a);
}

「hidemaruGlobal」のメソッドとして呼び出す為、hidemaruGlobal.filename2(); としていますが、「hidemaruGlobal.」は省略できます。 (通常はHidemaruGlobalのメソッドがグローバルに展開されている)

//filename2キーワードとmessage文の呼び出し
js {
    //HidemaruGlobalオブジェクトを省略
    a = filename2();
    message(a);
}

jsmode文を使うことで、明示的にhidemaruGlobalオブジェクトのメソッド等のグローバル展開を無効にしたり有効にしたりできます。 (デフォルトで「グローバル展開は有効」となっている)

jsmode文の説明

jsmode文は 2個のパラメータを指定します。詳細はマクロヘルプのjsmode文を参照。

パラメータ1
  • 数値を指定した場合は「グローバル展開の無効/有効」の指定(0 or 1)と見なされる。

    現状の「JavaScript実行環境」と「名前空間」でグローバル展開の無効(0)/有効(1)を設定します。グローバル展開の設定が異なると「別の実行環境」に切り替わることになります。

    newfile;
    debuginfo 2;
    //$mode = "WebView2";
    $mode = "JScript";
    
    jsmode $mode + @"\namesapce", 1;
    js{
        console.log("jsmode(0)=" + hidemaru.getJsMode());
        var name = "hidemaru";
        console.log("name(0)  =" + name);
    }
    jsmode 0;
    js{
        console.log("jsmode(1)=" + hidemaru.getJsMode());
        if(typeof(name) === "undefined"){
            console.log("name(1)  =宣言されていません。");
        }else if(hidemaru.getJsMode().indexOf("webview2", 0) == 0 && name == ""){
            console.log("name(1)  =''です。宣言されていないか、空文字がセットされています。" );
        }else{
            console.log("name(1)  =" + name);
        }
    }
    endmacro;
  • 文字列を指定した場合は、「JavaScript実行環境」と「名前空間」の指定となります。「名前空間」は「JavaScript実行環境」の後に "\" を付けて指定します。最初の "\" まで「JavaScript実行環境」とし、後は「名前空間」と認識します。

    jsmode "JScript\\namesapce";

    「JavaScript実行環境」はJavaScript実行環境を参照。「JavaScript実行環境」を省略した場合は、"JScript"を指定したことになります。

    「名前空間」を指定しない場合は、内部的にはマクロを実行しているファイル名(フルパス)が設定されます。

    //jsmode "実行環境の指定\名前空間", グローバル展開の無効/有効
    //"\"は"\\"と書くこと
    jsmode "JScript\\namesapce", 1;
    
    //逐語的リテラル文字列の記法を使えば"\"のエスケープは不要
    jsmode @"JScript\namesapce", 1;
    
    //「JavaScript実行環境」を省略した場合は"JScript"と見なす。
    jsmode @"\namesapce", 1;
    
    //「名前空間」を省略した場合は内部的には開いているファイルのフルパスが設定される。
    jsmode @"JScript\", 1;
    
  • パラメータ1に指定できる特殊な文字列として以下の2つがあります。

    • ""

      スクリプトエンジンを既定の「JScript」に戻し、次に実行する js{} または execjs はリセットされた状態で実行されます。 (パラメータ2は無視されます。)

      使い方が難しいです。

      1. jsmode @"JScript", 1; を指定して js{} を実行
      2. jsmode @""; を指定すると、「JScript」に戻るが、js{}は実行していない状態
      3. jsmode @"JScript", 1: を指定して js{} を実行

      上記内容だと、リセットされるのは 3) の「js{}」実行時です。「JScript」がリセットされます。

      newfile;
      debuginfo 2;
      $mode = "WebView2";
      jsmode $mode + @"\namesapce", 1;
      js{
          console.log("jsmode(0)=" + hidemaru.getJsMode());
          var name = "hidemaru";
          console.log("name(0)  =" + name);
      }
      jsmode @"", 0;
      //以下のjs{} 部分を有効にすると動作が変わります。
      //(「JScript」に戻っているので、リセット対象は「JScript」になる)
      /*
      js{
          console.log("jsmode(1)=" + hidemaru.getJsMode());
      }
      */
      jsmode $mode + @"\namesapce", 1;
      js{
          console.log("jsmode(2)=" + hidemaru.getJsMode());
          if(typeof(name) === "undefined"){
              console.log("name(2)  =宣言されていません。");
          }else if(hidemaru.getJsMode().indexOf("webview2", 0) == 0 && name == ""){
              console.log("name(2)  =''です。宣言されていないか、空文字がセットされています。" );
          }else{
              console.log("name(2)  =" + name);
          }
      }
    • "allreset"

      現在の秀丸エディタ上に残っている他の実行空間・名前空間を全てリセットします。

パラメータ2
  • 数値のみ(0 or 1)。グローバル展開の無効=0、グローバル展開の有効=1 になります。省略した場合は「1」を指定したと見なします。

jsmode文を実行すると、それ以降の「js{}」は設定した内容で実行されます。 jsmode文を実行したマクロから execmacro文 で呼び出したマクロ内の「js{}」も(jsmode文がなければ)同じ設定で実行されます。

オブジェクトの有効範囲

JavaScript では明示的にリセットしなければ、オブジェクト(変数、関数も含む)はウィンドウを閉じるまで保持されます。 JavaScript 内でグローバルスコープで宣言したものなら、他のマクロの JavaScript 内から参照できますが、以下の条件があります。

  1. 同じ秀丸エディタ上

    JavaScript を実行する秀丸エディタが異なれば、他の条件が一致していても別の実行環境となる。

  2. 同じJavaScript実行環境(スクリプトエンジン)

  3. 同じ名前空間

  4. 同じグローバル展開状態の場合

jsmode文を明示的に指定しないとデフォルト設定となるので条件が揃いやすいです。条件が揃えば別のマクロからでも JavaScript のオブジェクトにアクセス(読み書き変更)できます。つまり、自分で作成したマクロ以外から意図しない変更が加えられることも考えられます。

トラブルを避ける意味でも JavaScript を使うときには「名前空間」を指定しましょう。

execjs 文も同じで、execjs 文を実行した状態が有効範囲となります。

名前空間」は「他と衝突しないユニークな名前」が望ましいのですが、簡単に済ますなら以下の方法があります。

  • 「マクロファイル名(フルパス)」
  • 「マクロファイル名(ファイル名のみ)」+「開いているファイル名(ファイル名のみ)」
  • tickcountキーワード + 「マクロファイル名(ファイル名のみ)」または「開いているファイル名(ファイル名のみ)」
debuginfo 2;
jsmode @"JScript\" + currentmacrofilename;
js{
    console.log("hidemaru.getJsMode = " + hidemaru.getJsMode());
}

jsmode @"JScript\" + currentmacrobasename + "#" + basename;
js{
    console.log("hidemaru.getJsMode = " + hidemaru.getJsMode());
}

jsmode @"JScript\" + hex(tickcount & 0x7fffffff) + "#" + currentmacrobasename;
js{
    console.log("hidemaru.getJsMode = " + hidemaru.getJsMode());
}

「名前空間」は文字列長の制限や使えない文字はありませんが、"" や "allreset" は使わないようにしましょう。

jsmodeの設定状態確認

現状の jsmode文 の設定状態を取得するには、JavaScript内で hidemaru.getJsMode() 関数を使います。

JavaScript実行環境(スクリプトエンジン)、識別用文字列(名前空間)、グローバル展開の設定を文字列にして返します。(各項目の区切り文字として "\" を使用)

識別用文字列内に "\" が入る場合もあるので、"\" で分割した場合は、先頭は「スクリプトエンジン」、最後は「グローバル展開の設定」、その他が「識別用文字列」になります。

  • 動作確認用マクロ(デフォルト状態)

    //newfile
    //jsmode 0;
    debuginfo 2;
    js{
        //getJsMode()関数で取得
        var getmodestr   = hidemaru.getJsMode();
        //"\"で分解
        var getmode      = getmodestr.split("\\");
        //先頭を取り出し
        var ScriptEngine = getmode.shift();
        //最後を取り出し
        var GlobalFlag   = getmode.pop();
        //残りを合成
        var Distinction  = getmode.join("\\");
    
        console.log("hidemaru.getJsMode = " + getmodestr);
        console.log("ScriptEngine       = " + ScriptEngine);
        console.log("GlobalFlag         = " + GlobalFlag);
        console.log("Distinction        = " + Distinction);
    }
    endmacro;
  • 出力結果1

    「名前空間」を指定しない場合は、識別用文字列は開いているファイルのフルパスになります。

    hidemaru.getJsMode = jscript\c:\temp\test.txt\<global>
    ScriptEngine       = jscript
    GlobalFlag         = <global>
    Distinction        = c:\temp\test.txt

    newfile を有効にすると無題の新規ファイルを開くのでフォルダ名になります。

    hidemaru.getJsMode = jscript\c:\temp\\<global>
    ScriptEngine       = jscript
    GlobalFlag         = <global>
    Distinction        = c:\temp\
  • 出力結果2

    jsmode 0; のコメント部分を有効にして、グローバル展開の設定を変更してみます。

    hidemaru.getJsMode = jscript\c:\temp\\<strict>
    ScriptEngine       = jscript
    GlobalFlag         = <strict>
    Distinction        = c:\temp\
  • 出力結果3

    jsmode部分を変更。「JavaScript実行環境(スクリプトエンジン)」と「名前空間」を指定。

    jsmode @"WebView2\namesapce";

    識別用文字列(名前空間)が設定した文字列になります。

    hidemaru.getJsMode = webview2\namesapce\<global>
    ScriptEngine       = webview2
    GlobalFlag         = <global>
    Distinction        = namesapce
  • 出力結果4

    jsmode部分を変更。「JavaScript実行環境(スクリプトエンジン)」を指定し「名前空間」空白にする。

    jsmode @"WebView2\";

    「JavaScript実行環境(スクリプトエンジン)」は設定した文字列になります。識別用文字列は開いているファイルのフルパスになる。

    hidemaru.getJsMode = webview2\c:\temp\test.txt\<global>
    ScriptEngine       = webview2
    GlobalFlag         = <global>
    Distinction        = c:\temp\test.txt

「同期/非同期」とは

現時点(2024/08/05)では秀丸マクロが実行中かどうかという意味合い。

同期
秀丸マクロ実行中。
非同期
秀丸マクロが実行されていない状態。

非同期の状態では、秀丸マクロ実行中ではないので使用できるオブジェクトに制限があります。

  • hidemaruオブジェクトの幾つかのメソッド・プロパティは、非同期でも使用可能。
  • hidemaruGlobalオブジェクトは、基本的に非同期で使うことはできない。非同期で使うことができるものには、説明文の最初に[非同期]と記載されている。

秀丸マクロを制限なく使いたい場合は、hidemaru.postExecMacroFile や hidemaru.postExecMacroMemory で秀丸マクロを実行する方法もあります。

「非同期」のサンプルマクロ変更版

マクロ本体

//ファイル名:test_js.mac
hidemaruversion "9.22.14";
//アウトプット枠に出力
debuginfo 2;
debuginfo sprintf("%d:%s start\n", tickcount, currentmacrobasename);
js{
    //非同期で呼ばれる関数(秀丸マクロ実行中には呼び出されない)
    //この関数が呼ばれる時は、マクロの実行は終了しているので使用できる命令は「非同期」のみ
    function checkInterval(){
        console.log(tickcount() + ":[非同期] Call checkInterval() idInterval=" + idInterval  + "\n");
        //インターバル実行を解除(clearInterval は非同期でも使える)
        hidemaru.clearInterval(idInterval);
    }
    
    //秀丸マクロ実行中に実行される内容
    //setInterval の戻り値をグローバル変数で保存
    var idInterval;
    //インターバル実行を設定(checkInterval 関数を 1000ms毎に実行)
    idInterval = hidemaru.setInterval(checkInterval,1000);
    console.log(tickcount() + ":[同期] idInterval=" + idInterval + "\n");
}
debuginfo sprintf("%d:%s end\n", tickcount, currentmacrobasename);
endmacro;

実行結果

22375343:test_js.mac start                               ←マクロ開始
22375406:[同期] idInterval=23414                         遅延時間と呼び出される関数を指定
22375406:test_js.mac end                                 ←マクロ終了
22376406:[非同期] Call checkInterval() idInterval=23414  ← setIntervalで指定した時間後に checkInterval関数が実行されている。

実行結果を見ればわかりますが、checkInterval 関数はマクロ実行時には呼び出されず、マクロが終了してから setInterval で指定した時間経過後に呼び出されています。

JavaScript は明示的にリセットしなければ、オブジェクト(関数も含む)はウィンドウを閉じるまで保持されます。この性質を利用しグローバルスコープで変数を宣言すれば、どのタイミングでも変数にアクセス可能です。 (ただし、同じスクリプトエンジン・同じ名前空間・同じグローバル展開状態 の場合のみ)

なので、マクロを終了しても

idInterval = hidemaru.setInterval(checkInterval,1000);

idInterval の内容は保持されていので、checkInterval 関数内の

hidemaru.clearInterval(idInterval);

で、タイマーを止めることができます。

制限事項

秀丸エディタの切り替え

js{ }の実行中は、他の秀丸エディタへの切り替えができません。

従来のマクロでは、アクティブな秀丸ディタが切り替われば、マクロ実行する秀丸ディタも切り替わっていましたが、 js{ }内では切り替わりません。

  • 従来のマクロ

    debuginfo 1;
    openfile "a.txt";
    debuginfo sprintf("basename=%s\n",basename);
    openfile "b.txt";
    debuginfo sprintf("basename=%s\n",basename);
    endmacro;

    実行結果

    basename=a.txt
    basename=b.txt

    ファイルを開くと、アクティブな秀丸ディタも切り替わるので、マクロの実行も切り替わっている。 (basename が切り替わっている)

  • jsの場合

    debuginfo 1;
    openfile "a.txt";
    debuginfo sprintf("js:basename=%s\n",basename);
    js{
        openfile("b.txt");
        debuginfo(sprintf("js:basename=%s\n", basename()));
    }
    endmacro;

    実行結果

    js:basename=a.txt
    js:basename=a.txt

    ファイルを開くと、アクティブな秀丸ディタは切り替わるが、マクロの実行は切り替わっていない。 (basename が変わっていない)

数値のやり取り

数値を JavaScript と 秀丸マクロ 間でやり取りする場合は、64bit版でも、32bitまでという制限あり。

(hidemaruオブジェクトの)getVar/setVarによる JavaScript - 秀丸マクロ間の数値型変数とのやりとりは、32bitの数値として扱われる。

32bitの数値の上限は 2147483647、下限は -2147483648
  • サンプルマクロ

    debuginfo 2;
    #a = 0x123456789;
    debuginfo(sprintf("macro:#a=0x%llx\n", #a));
    js{
        var jsval1 = hidemaru.getVar("#a");
        hidemaruGlobal.debuginfo(hidemaruGlobal.sprintf("js{} :#a=0x%llx\n", jsval1));
        var jsval2 = 0x123456789;
        hidemaruGlobal.debuginfo(hidemaruGlobal.sprintf("js{} :#b=0x%llx\n", jsval2));
        hidemaru.setVar("#b", jsval2);
    }
    debuginfo(sprintf("macro:#b=0x%llx\n", #b));
    endmacro;
  • 実行結果

    macro:#a=0x123456789
    js{} :#a=0x23456789
    js{} :#b=0x123456789
    macro:#b=0x0
    • 32bit 以上の値を 秀丸マクロ → JavaScript の場合は、32bit で切り捨てられた値が JavaScript へ送られる。
    • 32bit 以上の値を JavaScript → 秀丸マクロ の場合は、0 が 秀丸マクロ へ送られる。

カラーマーカー

カラーマーカーを使用している場合は注意が必要です。

JavaScript を使用し、以下のような作業を行うと、カラーマーカーの情報は消えてしまいます。

  1. ファイルの内容をすべて取得
  2. JavaScriptで内容を変更
  3. 秀丸エディタ側の内容と入替

解析ツール + カラーマーカー の場合は再解析が必要になります。目印としてカラーマーカーを使っていた場合、カラーマーカーがなくなります。

カラーマーカーの情報も JavaScript 内から取得可能なので、カラーマーカーの情報を保存、入れ替えた内容に対して再適応させる方法もありますが、変更した内容に対し正しく反映させるられるかは不明です。

カラーマーカーを消えないようにするには、秀丸エディタ本体側の機能を使い内容を変更する必要があります。 (HidemaruGlobalオブジェクトを使用し、秀丸マクロの置換等のコマンドを使う)

JavaScript での処理内容によりますが、ファイル内容全体を対象とする場合は、カラーマーカーの有無を確認したほうがいいでしょう。


ページトップ 目次に戻る