ポップアップメニューで検索先にジャンプ

提供: MeryWiki
2020年6月25日 (木) 23:43時点におけるSukemaru (トーク | 投稿記録)による版 (sukemaru 版: 更新)
ナビゲーションに移動 検索に移動

概要

ポップアップメニューで検索にヒットした箇所へジャンプできます。

手石 版

機能

  • 選択した文字列(選択していないときはカーソルのある語)を検索します。
  • ヒットした行をポップアップメニューに表示し、選択するとその行へジャンプします。
  • 同一行にヒットする箇所が複数あったときは最初のヒット箇所にジャンプします。
  • 検索文字列の強調解除は、変更していなければ「Alt+F3」です。

コード (手石 版)

// pooupJump.js
// 2014/04/04

var sel = document.selection
if ( sel.isEmpty ){ sel.SelectWord(); };
var s = sel.Text;
var ty = sel.GetTopPointY( mePosView );
var p = sel.GetActivePos();
sel.Find( s, meFindNext + meFindAround );
sel.SetActivePos( p );

// ポップアップメニューでジャンプ先リスト
var menu_width = 66;
if ( "\t .,;:(){}[]+-=\"\\".indexOf( s ) < 0 && s.length > 0 ){
    var menu = CreatePopupMenu();
    var ary = [];
    for( var i = 0, n = document.GetLines( meGetLineView ); i < n ; i++ ){
        ary.push( document.GetLine( i+1, meGetLineView ));
    };
    var p = 0;
    for( var i = 0, n = ary.length; i < n ; i++ ){
        if ( ary[i].toLowerCase().indexOf( s.toLowerCase() ) >= 0 ){
            var item = ary[i].replace( /^\s*|\t/g, "" )
            item = ( item.length > menu_width + 1 )? item.slice( 0, menu_width ) + "..." : item ;
            var flags = ( (i+1) == ty )? meMenuChecked : 0 ; // meMenuGrayed  
            menu.Add( ( "    " + (i + 1) ).slice( -4 ) + " : " + item, i + 1, flags );
            p++;
        };
    };
    if ( p > 1 ){ // ポップアップメニューを表示するのは、検索ヒット行が複数のとき。
        var r = menu.Track( mePosMouse );  // mePosMouse 
        if ( r > 0 ){
            var line = document.GetLine( r, meGetLineView );
            sel.SetActivePoint( mePosView, line.indexOf( s ) + s.length + 1, r, false );
            sel.CharLeft( true, s.length );
            ScrollY = r-10;
        };
    };
};

履歴

  • 2014/04/04 初版

その他

  • 不具合があったら教えていただけると幸いです。


sukemaru 版

手石版をベースにしてカスタマイズしました。 手石版の 機能 にあげられている部分はそのまま踏襲しています。


ポップアップメニューを表示させるだけ(キャンセルする)であれば検索履歴には残らないようにしてありますので、検索ダイアログの最新の検索文字列を置きかえることなく、重複行の確認や選択文字列が出現する行の確認をできます。


「行の表示方法」の設定が表示座標( = 表示行・物理行)logical = false で、折り返しにより検索文字列が途切れている場合はヒットしません(Mery 標準の検索ダイアログでは検出できるのですが…)。


▼ 手石版からの変更点 ▼
  • エディタ設定の「行の表示方法」にあわせられるように、ポップアップメニューに表示する行番号は表示行/論理行を選択できます(ソースコード内の変数で設定)。
  • Mery 本体の検索ダイアログの履歴に検索文字列を残す/残さないを選択できます(ソースコード内の変数で設定)。
    ※ 検索文字列がほかの行でヒットしなかった場合や、ポップアップメニューからジャンプしなかった場合(キャンセル)には、検索履歴に残さないようにしてあります。
    ※ マクロの実行前に選択範囲なしだったときは単語選択もキャンセルします。
    ※ 検索文字列の強調表示を解除できます(ソースコード内の変数で設定)。
  • 検索文字列の禁止文字条件をなくしました。
  • 重複行のチェックをしやすくなるよう、論理行全体を範囲選択しているときは末尾改行を除外するようにしました。
  • ヒットした行数や文書全体の行数をステータスバーに表示します。
  • 「検索処理の所要時間」をステータスバーに表示できます(ソースコード内の変数で設定)。
  • 検索のさいに「大文字/小文字を区別」するかしないかを選択できます(ソースコード内の変数で設定)。


※ 行単位で検索するので、選択範囲が空行の場合や複数行の場合は検索できません。
※ ポップアップメニューは Esc キーまたは Space キーでキャンセルできます。


ダウンロード (sukemaru 版)

ダウンロード >> 「ファイル:ジャンプ.zip (アイコン入り)」 (2020/06/25 更新)


以下のマクロと、それぞれ専用の「マテリアルデザインっぽいアイコン」を同梱してあります。

ジャンプマクロ
おまけマクロ
  • 検索強調を解除
  • 検索オプションをリセット
を同梱

ソースコード (sukemaru 版)

最終更新: 2020/06/25

#title = "ジャンプ..."
#tooltip = "ポップアップメニューで検索先にジャンプ"
#icon = "arrows_swap_vertical[5].ico"
// #icon = "Mery用 マテリアルデザインっぽいアイコン.icl",229

/**
 * ------------------------------------------------------------
 * 「ポップアップメニューで検索先にジャンプ」
 *  Orginal created by: 手石 (2014/04/04)
 *  https://www.haijin-boys.com/wiki/ポップアップメニューで検索先にジャンプ
 * ------------------------------------------------------------
 * Modified by: sukemaru (2018/11/12 - 2020/06/25)
 * ------------------------------------------------------------
 */

var start = new Date();	// 所要時間の計測開始

// ---------- ▼ 設定項目 ▼ ---------- //

// ■行の表示方法 (true; 論理座標 /false; 表示座標)
var logical = true;

// ■検索文字列の強調表示を残すか? (true; 残す /false; 残さない)
var highlightEnable = true;

// ◆検索文字列を「検索履歴」に残さない? (true; 残さない / false; 残す)
// ※ historyDisable = true の場合、highliteEnable は無効になる(強調表示しない)
var historyDisable = true;	// ◆初期値 = true; (残さない)

// ◆「大文字/小文字」を区別するか? (true; 区別する / false; 区別しない)
var caseEnable = false;		// ◆初期値 = false; (区別しない)

// ◆検索処理の所要時間を表示するか? (true; 表示する /false; 表示しない)
var timerEnable = true;		// ◆初期値 = true; (する)

// ■ポップアップメニューに表示する文字数
var menuWidth = 66;

// ---------- ▲ 設定項目 ▲ ---------- //


var sx = ScrollX,  sy = ScrollY;
var d = editor.ActiveDocument,  s = d.selection;

// 論理行/表示行の設定を変数に
var posMode = logical ? mePosLogical : mePosView;
var lineMode = logical ? 0 : meGetLineView;

// 選択範囲なしならカーソル位置の単語を選択
if ( s.IsEmpty ) {
  var pos = s.GetActivePos();	// "選択範囲なし" フラグ
  s.SelectWord();
}

// 検索文字列
var st = s.Text;
// 選択範囲の末尾が改行 "\n" なら検索文字列から除外する
if ( st && st.charAt( st.length - 1 ) === "\n" ) {
  st = st.slice( 0, st.length - 1 );
  // 自動マーカーを効かせるために選択範囲も調整する
  var adjust = true;	// 調整フラグ
  var anc = s.GetAnchorPos(),  act = s.GetActivePos();
  if ( anc < act ) { s.SetActivePos( act - 1, true ); }
  else             { s.SetAnchorPos( anc - 1 ); }
}

// 選択範囲の先頭と文書全体の行数(論理行/表示行)
var ty = s.GetTopPointY( posMode );
var by = s.GetBottomPointY( posMode );
var lines = d.GetLines( lineMode );
var linesStatus = " / 全体の行数: " + SeparateNum( lines )
                + ( logical ? " 行 (論理行)" : " 行 (表示行)" );

var cancel = -1;
// 検索文字列がない(空行)
if ( ! st ) { cancel = 1; }
// 検索条件エラー(改行)
else if ( /\n/g.test( st ) ) { cancel = 2; }
// 検索条件エラー(複数行: 「表示行」& 選択範囲に折り返しあり)
else if ( ! logical && ty !== by ) { cancel = 3; }

// ポップアップメニューでジャンプ先リスト
else {
  for( var i = 1, a = [ "" ]; i <= lines ; i ++ ) {
    a.push( d.GetLine( i, lineMode ) );
  }

  // 検索文字列がヒットした行をポップアップメニューのアイテムとして取得する
  var m = CreatePopupMenu();
  var numWidth = lines.toString().length;

  for ( var i = 1, h = 0, len = a.length, num, label, flags;
  i < len; i ++ ) {
    if ( FindCaseTF( a[i], st ) > -1 ) {
      num = ( "       " + i ).slice( - numWidth ) + ": ";
      // 行頭空白は除去、空白文字は半角スペースに置換、文字数を圧縮して取得
      // 判別しづらい半角メタ文字を全角に置きかえる
      label = a[i].replace( /^\s+/, "" )
                  .replace( /\t+|[ ]{3,}/g, "  " )
                  .replace( /[.,:;(){}]/g, function( $0 ) {
                    return String.fromCharCode( $0.charCodeAt( 0 ) + 0xFEE0 )
                  } );
      label = ( label.length > menuWidth )
            ? label.slice( 0, menuWidth ) + "..." : label;
      flags = ( i === ty ) ? meMenuChecked : 0;
      m.Add( num + label, i, flags );
      h ++;	// ヒットした行数
    }
  }

  m.Add( "", 0, meMenuSeparator );
  m.Add( "キャンセル\t& ", 0 );

  // 検索開始からポップアップメニューを表示するまでの所要時間を取得
  if ( timerEnable ) {
    var elapsed = new Date() - start;
    var timerStatus = "  [ "
                    + ( elapsed / 1000 ).toFixed( 3 ).replace( /\./, ". " )
                    + " 秒 ]";
  }

  // ポップアップメニューを表示するのは、検索ヒット行が複数のとき
  if ( h > 1 ) {
    Status = " ヒットした行数: " + SeparateNum( h ) + " 行"
           + linesStatus + ( timerEnable ? timerStatus : "" );
    // Track(0) ならキャレット位置にポップアップ
    var y = m.Track( mePosMouse );

    // 選択した行番号にジャンプして検索文字列を選択
    if ( y > 0 ) {
      s.SetActivePoint( posMode, 1, y );
      // 検索履歴に残さない ※「検索文字列の強調表示」なし
      if ( historyDisable ) {
        s.SetActivePos( s.GetActivePos() + FindCaseTF( d.GetLine( y, lineMode ), st ) );
        s.SetAnchorPos( s.GetActivePos() + st.length );
      }
      // 検索履歴に残す ※ Mery の検索機能で文字列を範囲選択
      else {
        s.Find( st, meFindNext + ( caseEnable * 2 ) );
        // 「検索文字列の強調表示」を解除する/しない
        d.HighlightFind = highlightEnable;
      }
      cancel = 0;	// cancel フラグを解除
    }
    else { cancel = 4; }	// ポップアップメニューをキャンセル
  }
  else { cancel = 5; }	// ほかの行でヒットなし
}

// ジャンプしなかったとき
if ( cancel ) {
  if      ( pos > -1 )            { s.SetActivePos( pos ); }
  else if ( adjust && anc < act ) { s.SetActivePos( act, true ); }
  else if ( adjust && act < anc ) { s.SetAnchorPos( anc ); }
  // キャンセルフラグの値が 1 or 2 のとき、所要時間の計測終了
  if ( timerEnable && ! elapsed ) {
    var elapsed = new Date() - start;
    var timerStatus = "  [ "
                    + ( elapsed / 1000 ).toFixed( 3 ).replace( /\./, ". " )
                    + " 秒 ]";
  }
  Status = ( ( cancel === 1 ) ? " 検索文字列がありません ( 空行 )。"
           : ( cancel === 2 ) ? " 無効な検索文字列 ( 改行 )。"
           : ( cancel === 3 ) ? " 無効な検索文字列 ( 折り返し )。"
           : ( cancel === 4 ) ? " キャンセル。 ヒット: "
                                + SeparateNum( h ) + " 行"
           : ( cancel === 5 ) ? " ヒットなし。"
           :/* cancel == -1 */ " 無効な検索文字列。" )
         + linesStatus + ( timerEnable ? timerStatus : "" );
  ScrollX = sx;  ScrollY = sy;
}

/* 関数 FindCaseTF( str, word ) */ 
// 文字列 str 内で st を検索するさいに「大文字/小文字を区別」する/しない
// ヒットすればヒットした位置(  0 ~ )、ヒットしなければ -1 を返す
function FindCaseTF( str, word ) {
  return caseEnable ? str.indexOf( word )
         /* else */ : str.toLowerCase().indexOf( word.toLowerCase() )
}

/* 関数 SeparateNum( num ) */
// 数値を3ケタ区切りにする
function SeparateNum( num ) {
  return num.toString().replace( /(\d)(?=(?:\d{3})+$)/g, "$1," )
}


include版

動作設定の詳細なカスタマイズが可能で、設定の変更はポップアップメニュー内の項目のチェック ON/OFF でできます。

スクリーンショット

スクリーンショット画像に表示されている追加オプションを使用したい方は ZIP 書庫 内の「include版(メニューでの設定変更に対応)」フォルダ内の JS ファイルをお使いください。

  • includeライブラリ」を使用し、設定内容を外部ファイルに保存します。
    あらかじめ includeライブラリ をインストールしてください。
  • 設定内容の保存場所は Mery\Macros\MacroSettings\<ジャンプ>.json
    または %AppData%\Mery\MacroSettings\<ジャンプ>.json です。
    <ジャンプ> の部分はこのマクロのファイル名


  • 行番号の桁埋め(右寄せ/空白埋め)用の空白文字を設定する項目 var blankChr = " "; を追加し、初期値を半角空白 " "(U+0020)にしました(※ これまで MS UI Gothic に最適化してあった桁埋め方法をカスタマイズできるようにした)。
MS UI Gothic では2分アキ(EN SPACE) "\u2002"
Meiryo UI では和字間隔(全角空白) "\u3000"
Segoe UI では図形間隔(FIGURE SPACE) "\u2007" にすると具合がよいようです。


ソースコード(include版)

  • このマクロではポップアップメニューの表示に unicode 文字を使用します。
    ファイルに保存するさいは、文字コードを shift_JIS にしないでください(UTF-8 や Unicode で保存する)。
#title = "ジャンプ..."
#tooltip = "ポップアップメニューで検索先にジャンプ"
#include "include/IO.js"
#icon = "arrows_swap_vertical[5].ico"
// #icon = "Mery用 マテリアルデザインっぽいアイコン.icl",229

/**
 * ---------------------------------------------------------
 * 「ポップアップメニューで検索先にジャンプ」
 *   Orginal created by: 手石 (2014/04/04)
 *   https://www.haijin-boys.com/wiki/ポップアップメニューで検索先にジャンプ
 * ---------------------------------------------------------
 *   Modified by:  sukemaru (2018/11/21 - 2020/06/25)
 *  【include版】: sukemaru (2018/12/16 - 2020/06/25)
 * ---------------------------------------------------------
 */

var start = new Date();		// 所要時間の計測開始

// ---------- ▼ 【include版】 追加設定項目 ▼ ---------- //

// ◆このマクロの「設定を変更するための項目」をポップアップメニューに表示するか?
var settingEnable = true;

// ◆JSON ファイルのベース名
var jsonName = ScriptName.replace( /\.js$/i, "" );

  // 任意の名前に変更する場合はアンコメントして書き換える
  // jsonName = "ポップアップメニューで検索先にジャンプ";


// ■ 連番アイテムの桁埋め(右寄せ/空白埋め)用空白文字の定義
var blankChr = " ";	// " " or "\u2002" or "\u3000" or "\u2007" or ...

  /* 半角数字の幅に等しい空白文字として
     MS UI Gothic では「2分アキ」 = EN SPACE: "\u2002"
     (MeiryoKe_UI Gothic は MS UI Gothic に同じ)
     Meiryo UI では「和字間隔」 = 全角空白: "\u3000"
     Segoe UI では「図形間隔」 = FIGURE SPACE: "\u2007"
     にすると具合がよさそうですが、ウエイトによって幅が変わるので... */


// ---------- ▼ 【通常版/人柱版】 初期設定項目 ▼ ---------- //

// 以下、【include版】ではポップアップメニューから変更すること
var caseEnable = false;
var onlyWordEnable = false;
var blankLineEnable = true;
var historyDisable = true;
var highlightEnable = false;
var autoLineModeEnable = false;
var logical = true;
var timerEnable = true;
var countEnable = true;
var menuScrollEnable = true;
var subMenuEnable = true;
var subMenuStyle = 1;
var subMenuHeight = 30;
var omitEnable = true;
var menuWidth = 55;
var toWideWidth = 0;
var menuPosMouse = true;

// ---------- ▲ 設定項目 ここまで ▲ ---------- //


/* ▼ 【include版】の追加コード ▼ */ 
// 「設定変更」メニューの表示許可や JSON の初期化に使用する初期設定
var initialSettings = setting = { settingEnable: false };
if ( settingEnable && typeof IO === "object" ) {
  // ソースコードの「設定項目」内の初期値を保存する
  initialSettings = setting = {
    settingEnable:		settingEnable,
    caseEnable:			caseEnable,
    onlyWordEnable:		onlyWordEnable,
    blankLineEnable:	blankLineEnable,
    historyDisable:		historyDisable,
    highlightEnable:	highlightEnable,
    autoLineModeEnable:	autoLineModeEnable,
    logical:			logical,
    timerEnable:		timerEnable,
    countEnable:		countEnable,
    menuScrollEnable:	menuScrollEnable,
    subMenuEnable:		subMenuEnable,
    subMenuStyle:		subMenuStyle,
    subMenuHeight:		subMenuHeight,
    omitEnable:			omitEnable,
    menuWidth:			menuWidth,
    toWideWidth:		toWideWidth,
    menuPosMouse:		menuPosMouse
  };
  // JSON ファイルから設定を読み込む
  jsonName = jsonName || ScriptName.replace( /\.js$/i, "" );
  setting = IO.Deserialize( setting, jsonName );
  // 通常版の設定変数に再代入(通常版/人柱版とコードを共通化する)
  settingEnable			= setting.settingEnable;
  caseEnable			= setting.caseEnable;
  onlyWordEnable		= setting.onlyWordEnable;
  blankLineEnable		= setting.blankLineEnable;
  historyDisable		= setting.historyDisable;
  highlightEnable		= setting.highlightEnable;
  autoLineModeEnable	= setting.autoLineModeEnable;
  logical				= setting.logical;
  timerEnable			= setting.timerEnable;
  countEnable			= setting.countEnable;
  menuScrollEnable		= setting.menuScrollEnable;
  subMenuEnable			= setting.subMenuEnable;
  subMenuStyle			= setting.subMenuStyle;
  subMenuHeight			= setting.subMenuHeight;
  omitEnable			= setting.omitEnable;
  menuWidth				= setting.menuWidth;
  toWideWidth			= setting.toWideWidth;
  menuPosMouse			= setting.menuPosMouse;
}
/* ▲ 【include版】の追加コード ▲ */ 

/* ■ポップアップメニューの「行番号の表示方法」 */
// Mery.ini から「行の表示方法」を取得する
if ( autoLineModeEnable ) {
  logical = ! GetIniOptionNum( "LineColumnView" );
}
var posMode  = logical ? mePosLogical : mePosView;
var lineMode = logical ? 0 : meGetLineView;

var d = editor.ActiveDocument;

// 文書全体の行数(論理行/表示行) ( 論理行 )
var lines = d.GetLines( lineMode );
var lineStatus = "全体の行数: " + lines + " 行"
               + ( logical ? "" : " ( 表示行 )" );

/* 関数 TimerElapsed( end, start )
   start からの経過時間を [ s.sss 秒 ] で返す */
var TimerElapsed = function( end, start ) {
  return "  [ "
  +  ( ( end - start ) / 1000 ).toFixed( 3 ).replace( /\./, ". " )
  + " 秒 ]";
};
// 9,999,999 行 制限オーバーのときは終了
if ( lines >= 10000000 ) {
  Status = SeparateNum( " 9,999,999 行 制限オーバー。 文書"
         + lineStatus + TimerElapsed( new Date(), start ) );
}
else {
  // 連番の桁埋め(右寄せ/空白埋め)用
  var b1 = ( ! blankChr || ( "" + blankChr ).length > 1 )
         ? " " : blankChr;
  /**
   * Object.prototype.PadSpace( len, chr )
   * Object オブジェクトに拡張メソッドを追加(グローバル)
   * 数字や文字列の左側を chr で桁埋めした文字列をかえす
   */
  Object.prototype.PadSpace = function(len, chr) {
    var dgt = String(this);
    len = Number(len) || dgt.length;
    chr = chr ? chr.toString() : b1; // (b1 = blankChr || " ")
    if (len < 0 || len < dgt.length || chr.length !== 1) 
      return dgt;
    for (var i = 0; i < len; i++)
      chr += chr;
    return (chr + dgt).slice( - len);
  };

  /* 検索文字列の準備 */
  var s = d.selection;
  var sx = ScrollX,  sy = ScrollY;
  var isJS = ( /javascript|mery[ _]?macros?[ _]?js/i.test( d.Mode )
            || /\.js[^.]?$/i.test( d.Name ) );

  // 「英単語のみ」オプションが有効のときは、選択範囲の有無によらず
  // キャレット付近の「単語」を自動選択する(キャンセルしても単語選択のまま残す)
  if ( onlyWordEnable && /\W/.test( s.Text ) ) {
    s.SetActivePos( s.GetActivePos() );
    s.WordLeft();  s.SelectWord();
  }

  // 選択範囲なしならキャレット位置の単語を自動選択する
  var pos = s.IsEmpty ? s.GetActivePos() : -1;	// "選択範囲なし" のフラグ
  if ( pos >= 0 ) {
    s.SelectWord();
    // 先頭部分に $ があるならそれも含める(JavaScript の $変数名 に対応)
    if ( isJS && /\w/.test( s.Text.charAt( 0 ) )
    && d.Text.charAt( s.GetAnchorPos() - 1 ) === "$" ) {
      s.SetAnchorPos( s.GetAnchorPos() - 1 );
    }
  }

  // 検索文字列
  var st = s.Text;
  var ty = s.GetTopPointY( posMode );
  var by = s.GetBottomPointY( posMode );
  var anc = s.GetAnchorPos();
  var act = s.GetActivePos();
  var blankLineMode = false;	// "空白行検索モード"
  /* 関数 IsBlank( str )
     空白行の正規表現テスト */
  var IsBlank = function( str ) {
    return /^[\t  ]*$/.test( str );
  };

  // 選択範囲の末尾が改行 "\n" なら検索文字列から除外する
  if ( ! st || st.charAt( st.length - 1 ) === "\n" ) {
    st = st.replace( /\n$/, "" );
    // "空白行検索モード"
    if ( blankLineEnable && IsBlank( st ) ) {
      blankLineMode = true;
    }
    // 自動マーカーを効かせるために選択範囲も末尾調整する
    else if ( anc < act ) {
      s.SetActivePos( act - 1, true );
    }
    else if ( act < anc ) {
      s.SetAnchorPos( anc - 1 );
      s.SetActivePos( act, true );
    }
  }
  // 文書末尾の空白行の場合でも "空白行検索モード"
  else if ( ty === lines && st === d.GetLine( ty, lineMode )
  && blankLineEnable && IsBlank( st ) ) {
    blankLineMode = true;
  }

  // アクティブ行が見える位置にスクロールさせる(ちょっと強引)
  var moveOne = ( act < anc ) ? 1 : -1;
  s.SetActivePos( s.GetActivePos() + moveOne, true );
  s.SetActivePos( s.GetActivePos() - moveOne, true );

  // countEnable での JS の変数・関数定義の検索フラグを確定する
  if ( ! countEnable || blankLineMode
  || ! /^\s*[$]?[_a-z][\w]*\s*$/i.test( st ) ) {
    isJS = false;
  }

  /* "キャンセル" フラグ */ 
  var cancel = (
  // 検索文字列がないとき(空行)
    ( ! blankLineEnable && ! st ) ? 1
  // 検索条件エラー(複数行: 選択範囲に改行あり)
  : ( st.indexOf( "\n" ) > -1 ) ? 2
  // 検索条件エラー(複数行: 「表示行」& 選択範囲に折り返しあり)
  : ( ! logical && ty !== by ) ? 3
  // 検索条件エラー(「英単語のみ」で検索文字列が非・英単語のとき)
  : ( ! blankLineMode && onlyWordEnable && /\W/.test( st ) ) ? 4
  // エラーなし
  : /* else */ -1
  );


  /* ポップアップメニューでジャンプ先リスト */
  if ( cancel < 0 ) {
    var num = label = line = "", flags = numWidth = 0;

    /* 文書全体を行単位で分割し、配列に */
    var a = [ "" ];	// a[0] = 空要素、index = 行番号
    for ( var i = 1; i <= lines; i ++ ) {
      a.push( d.GetLine( i, lineMode ) );
    }

    /* 「文字列」の検索 */
    if ( ! blankLineMode ) {
      /* 関数 Quote( str )
         正規表現での検索用の退避修飾 */
      var Quote = function( str ) {
        return str.replace( /[.*+?^=!:${}()|[\]\/\\]/g, "\\$&" );
      };
      var stQuote = Quote( st );
      var countReg = RegExp(
        onlyWordEnable ? "\\b" + stQuote + "\\b" : stQuote
      , caseEnable ? "g" : "gi" );
      /** 関数 FindCaseTF( strLine, word, onlyWordEnable, caseEnable, countReg )
         ヒットした位置(index)を返す(ハズレは -1)*/
      var FindCaseTF = function( line, word, onlyWordEnable, caseEnable, countReg ) {
        return onlyWordEnable ? line.search( countReg )
             : caseEnable     ? line.indexOf( word )
             : line.toLowerCase().indexOf( word.toLowerCase() );
      }
      var regBlankChar = /[\u00A0\u1680\u180E\u2000-\u200E\u2028\u2029\u202F\u205F\u2062\u2063\uFEFF]/g;
      var mArray = [],  gtArray = [],  vArray = [];
      var hit = 0,  check = "",  jumpID;
      var gt = 0,  v = 0,  t,  me,  im,  hitLines;
      var activeLine = d.GetLine( ty, lineMode );
      var tx = s.GetTopPointX( posMode );

      for ( var i = 1, len = a.length; i < len; i ++ ) {
        jumpID = FindCaseTF( a[i], st, onlyWordEnable, caseEnable, countReg );
        if ( jumpID < 0 ) { continue; }
        line = a[i];
        label = Labeling( line, st, menuWidth, toWideWidth, b1, regBlankChar );
        flags = ( i === ty ) ? meMenuChecked : 0;	// アクティブ行
        a[i] = jumpID;	// 出現位置のインデックス

        // 「ヒット総数」フラグが有効の場合
        if ( countEnable ) {
          // gtArray にヒット総数とマーキング情報、check の値を代入
          gtArray.push( { id: i,  gt: gt } );	// 行番号と前の行までの総ヒット回数
          t = line.match( countReg ).length;	// 行内でのヒット回数
          // 選択範囲(アクティブ行・検索開始位置)の検索文字列が「何件目のヒット」か
          if ( flags ) {
            me = ( t === 1 ) ? gt + 1
            : gt + 1 + ( line.substr( 0, tx - 1 ).match( countReg ) || [] ).length;
            hitLines = mArray.length + 1;
          }
          gt += t;	// 総ヒット回数
          // 右側につけるマーキング
          check = (
            // ①アクティブ行(行検索のとき)  「⏎」
            ( i === ty && line === st ) ? "\t \u23CE"
            // ②検索文字列が複数ヒットした行  「x2」
          : ( t > 1 ) ? "\t ×" + t
            // ③アクティブ行と完全一致する行(重複行)  「**」
          : ( i !== ty && line === activeLine ) ? "\t dup"	
            // ④検索文字列と完全一致する行  「*」
          : ( line === st ) ? "\t *"
            // ⑤検索文字列をひとつだけ含む行
          : /* else */ ""
          ) + ( isJS ? JsMarking( line, st, stQuote ) : "" );
          label += check;
          // 変数/関数の宣言行はメニューの先頭にも置く
          if ( check.indexOf( "←" ) > -1 ) {
            vArray.push( { str: label,  id: i, flag: flags } );
          }
        }

        // 「検索文字列がヒットした行」を配列に格納
        mArray.push( { str: label,  id: i,  flag: flags } );
      }
      hit = mArray.length;	// ヒットした行数の総数
      v = vArray.length;	// JS の変数/関数の宣言行・代入行の総数
      numWidth = Math.min( mArray[ hit - 1 ].id, lines ).toString().length;
    }

    /* 「空行+空白行」の検索 */
    else {
      var hArray = [],  nArray = [],  wArray = [];
      var b = 0,  h = 0,  n = 0,  w = 0;

      for ( var i = 1, tmp, len = a.length; i < len; i ++ ) {
        if ( ! IsBlank( a[i] ) ) { continue; }
        b ++;	// 空行+空白行の総数: Blank line ( n + w )
        line = a[i];
        label = Labeling( line, st, menuWidth, toWideWidth );
        flags = ( i === ty ) ? meMenuChecked : 0;	// アクティブ行
        tmp = { str: label,  id: i,  flag: flags };
        if ( line === st )  { hArray.push( tmp ); }
        if ( st && ! line ) { nArray.push( tmp ); }
        else if ( line )    { wArray.push( tmp ); }
      }
      h = hArray.length;	// 完全一致した行数: Hit ( match completely )
      n = nArray.length;	// 空行数: New line ( /^$/ )
      w = wArray.length;	// 空白行数: White space ( /^[\t  ]+$/ )
      numWidth = Math.min( hArray[ h - 1 ].id, lines ).toString().length;
    }


    /* ポップアップメニューの準備 */
    var menuIndent = "".PadSpace( numWidth, b1 );
    var m = CreatePopupMenu();

    if ( menuScrollEnable
    && ( hit > subMenuHeight && ! omitEnable )
    || h > subMenuHeight ) {
      // Space キーでキャンセル可(またはスクロール)
      m.Add( menuIndent + " キャンセル\t& ", 0 );
      m.Add( "", 0, meMenuSeparator );
    }

    /* 【include版】の設定変更サブメニュー */
    if ( initialSettings.settingEnable ) {
      var setMenu = SettingsMenu( setting, menuIndent );
      m.AddPopup( menuIndent + " 設定を変更する (&S)", setMenu );
      m.Add( "", 0, meMenuSeparator );
    }

    /* 「検索文字列」のポップアップメニュー */
    if ( ! blankLineMode ) {
      // 「分割サブメニュー」をピン止め
      if ( subMenuEnable && hit > subMenuHeight ) {
         var subMenu = DevidedMenu( m, mArray, hit, numWidth, subMenuStyle, subMenuHeight, b1 );
      }
      // ヒットした行が subMenuHeight より多いときは「※さいごの行※」をピン止め
      if ( hit > subMenuHeight ) {
        m.Add( "※".PadSpace( numWidth - 1, b1 )
          + " さいごの行 (&E) ( 行番号: " 
          + SeparateNum( mArray[ hit - 1 ].id ) + " ) ※"
        , mArray[ hit - 1 ].id );
        m.Add( "", 0, meMenuSeparator );
      }

      // 「ヒット件数(出現総数)」オプションが有効のばあい
      if ( v > 0 ) {
        // 変数/関数の宣言行・代入行をピン止め
        for ( var i = 0; i < v; i ++ ) {
          line = vArray[i];
          num = line.id.PadSpace( numWidth, b1 ).replace( /\d$/, "&$&: " );
          m.Add( num + line.str, line.id, line.flag );
        }
        m.Add( "", 0, meMenuSeparator );
      }

      // 「検索文字列がヒットした行」をポップアップメニューに追加する
      if ( hit > v ) {
        m.Add( "", 0, meMenuSeparator );

        // ステータスをピン止め
        var gtStatus = ( gt > 0 )
                     ? "  ( " + hitLines + " 行目 )  ・  "
                       + "ヒット数: 全 " + gt + " 件  ( " + me + " 件目 )"
                     : " / " + lineStatus;
        m.Add( SeparateNum( "▼".PadSpace( numWidth - 1, b1 )
          + "  ヒットした行: 全 " + hit + " 行" + gtStatus + "  ▼" )
        , 0, 0 );

        // メインメニュー直下に置くアイテムの行数
        // ※ 分割メニューを表示するときの、メインメニューに表示する行数の制限
        var mainMenuHeight = ( subMenuEnable && omitEnable )
                           ? Math.min( hit, subMenuHeight ) : hit;
        // 「ヒットした行」をメインメニューに追加
        for ( var i = 0; i < mainMenuHeight; i ++ ) {
          line = mArray[i];
          num = line.id.PadSpace( numWidth, b1 ).replace( /\d$/, "&$&: " );
          m.Add( num + line.str, line.id, line.flag );
          // 「キャンセル」行の間隔* = subMenuHeight
          AddCancelLines( m, i, mainMenuHeight, subMenuHeight, menuIndent, menuScrollEnable );
        }

        // メインメニューが通常表示のとき
        if ( menuScrollEnable && hit > subMenuHeight
        && mainMenuHeight === hit ) {
          m.Add( "", 0, meMenuSeparator );
          m.Add( "▲".PadSpace( numWidth - 1, b1 )
            + "  ヒットしたさいごの行  ▲\t& ", 0 );
        }
        // 分割メニューを表示しているとき
        if ( subMenuEnable && subMenuStyle
        && omitEnable && hit > subMenuHeight ) {
          m.Add( "", 0, meMenuSeparator );
          m.AddPopup( "※".PadSpace( numWidth - 1, b1 ) + "  その他 "
            + SeparateNum( hit - subMenuHeight ) + " 件のヒットあり"
            + ( mainMenuHeight > subMenuHeight ? "  ※ & " : " (&M)  ※" )
          , subMenu );
        }
      }
    }

    /* 「空行+空白行」のポップアップメニュー */
    if ( blankLineMode && b > 0 ) {
      // 「すべての空行+空白行」の総数をピン止め
      if ( b !== h ) {
        m.Add( menuIndent + " すべての空行+空白行: "
          + SeparateNum( b ) + " 行", 0 );
        m.Add( "", 0, meMenuSeparator );
      }
      // 「すべての空行」サブメニュー
      if ( n > 0 && st ) {
        var sm1 = CreatePopupMenu();
        m.AddPopup( menuIndent + " すべての空行: "
          + SeparateNum( n ) + " 行 (&N)", sm1 );
        if ( menuScrollEnable && n > subMenuHeight ) {
          sm1.Add( menuIndent + " キャンセル\t& ", 0 );
          sm1.Add( "", 0, meMenuSeparator );
        }
        for ( var i = 0; i < n; i ++ ) {
          line = nArray[i];
          num = line.id.PadSpace( numWidth, b1 ).replace( /\d$/, "&$&: " );
          sm1.Add( num + line.str, line.id, line.flag );
          AddCancelLines( sm1, i, n, subMenuHeight, menuIndent, menuScrollEnable );
        }
      }

      // 「すべての空白行」サブメニュー
      if ( w > 0 && n + h !== b ) {
        var sm2 = CreatePopupMenu();
        m.AddPopup( menuIndent + " すべての空白行: "
          + SeparateNum( w ) + " 行 (&W)", sm2 );
        if ( menuScrollEnable && w > subMenuHeight ) {
          sm2.Add( menuIndent + " キャンセル\t& ", 0 );
          sm2.Add( "", 0, meMenuSeparator );
        }
        for ( var i = 0; i < w; i ++ ) {
          line = wArray[i];
          num = line.id.PadSpace( numWidth, b1 ).replace( /\d$/, "&$&: " );
          sm2.Add( num + line.str, line.id, line.flag );
          AddCancelLines( sm2, i, w, subMenuHeight, menuIndent, menuScrollEnable );
        }
      }
      if ( ( n > 0 && st ) || ( w > 0 && n + h != b ) ) {
        m.Add( "", 0, meMenuSeparator );
      }
      // 完全一致した空白行
      if ( h > 0 ) {
        // キャンセルを兼ねる(Space キーでキャンセル可)
        m.Add( "▼".PadSpace( numWidth - 1, b1 )
          + "  一致した行: " + SeparateNum( h ) + " 行  ▼\t& ", 0 );
        // ヒットした行が subMenuHeight より多いときは「※さいごの行※」を追加
        if ( h > subMenuHeight ) {
          m.Add( "※".PadSpace( numWidth - 1, b1 )
            + "  さいごの行 ( 行番号: " 
            + SeparateNum( hArray[ h - 1 ].id ) + " )  ※"
          , hArray[ h - 1 ].id );
          m.Add( "", 0, meMenuSeparator );
        }
        // 「一致した行」をメインメニューに追加
        for ( var i = 0; i < h; i ++ ) {	// h == hArray.length
          line = hArray[i];
          num = line.id.PadSpace( numWidth, b1 ).replace( /\d$/, "&$&: " );
          m.Add( num + line.str, line.id, line.flag );
          AddCancelLines( m, i, h, subMenuHeight, menuIndent, menuScrollEnable );
        }
      }
    }

    // 最下行にピン止め(Space キーでスクロール可)
    if ( menuScrollEnable && h > subMenuHeight ) {
      m.Add( "", 0, meMenuSeparator );
      m.Add( "▲".PadSpace( numWidth - 1, b1 )
        + "  一致したさいごの行  ▲\t& ", 0 );
    }

    /* 検索開始からポップアップメニューを表示するまでの所要時間を取得 */
    var timerStatus = ( timerEnable )
                    ? TimerElapsed( new Date(), start ) : "";

    /* ポップアップメニューを表示するのは、検索ヒット行が複数のとき */
    if ( initialSettings.settingEnable || hit || b ) {

      /* 関数 FindStatus()
         ヒット数や所要時間の情報など */
      var FindStatus = function() {
        // me: ジャンプする前の位置 / im: ジャンプした後の位置
        return ( blankLineMode
          ? " 一致: " + h + " 行 / 空白行数: " + b + " 行 / "
          : ( gt ? " ヒット数: " + gt + " 件 ( "
                   + ( im ? im : me ) + " 件目 ) ・ "
                   +  hit + " 行 ( " + hitLines + " 行目 ) / "
                 : " ヒット: " + hit + " 行 / " )
        ) + lineStatus + timerStatus;
      };
      /* ステータスとポップアップメニューを表示 */
      var findStatus = FindStatus();
      Status = SeparateNum( findStatus );
      var y = m.Track( menuPosMouse );

      /* 【include版】の設定変更サブメニュー項目を選択した場合 */
      if ( y >= 10000000 ) {
        SettingsChange( y, setting, jsonName );
        var isChanged = true;
      } // else

      /* 選択した行番号(行頭)にジャンプして、検索文字列を範囲選択 */
      if ( y > 0 && y < 10000000 ) {
        s.SetActivePoint( posMode, 1, y );	// とりあえず行頭

        // 文字列でジャンプしたとき
        if ( ! blankLineMode ) {
          // 検索履歴に残さない	※「検索文字列の強調表示」なし
          if ( historyDisable ) {
            s.SetActivePos( s.GetActivePos() + a[y] );
            s.SetAnchorPos( s.GetActivePos() + st.length );
            d.HighlightFind = false;
          }
          // 検索履歴に残す	※ Mery の検索メソッドで文字列を範囲選択
          else {
            // 「大文字/小文字」フラグと「英単語のみ」フラグを調整
            s.Find( st, meFindNext + ( caseEnable * 2 ) + ( onlyWordEnable * 4 ) );
            // 「検索文字列の強調表示」を解除する/しない
            d.HighlightFind = highlightEnable;
          }
          // ジャンプ後の選択範囲が何件目のヒットかを取得する
          if ( countEnable ) {
            var im;
            for ( var i = 0, len = gtArray.length; i < len; i ++ ) {
              if ( gtArray[i].id === y ) {
                im = gtArray[i].gt + 1;
                hitLines = i + 1;
                break;
              }
            }
          }
        }
        // 空白行でジャンプしたときは検索履歴に残さない
        else if ( blankLineMode && d.GetLine( y, lineMode ) ) {
          // 行(空白文字列)全体を範囲選択する(※完全な空行では範囲選択しない)
          s.SetAnchorPoint( posMode, 1, y + 1 );
        }
        ScrollY = ( ScrollY === sy )
                ? sy : s.GetActivePointY( mePosView );
        cancel = 0;
      }

    /* キャンセル */
      // ポップアップメニューをキャンセル ( y == 0 )
      else { cancel = 5; }
    }
    // ヒットなし
    else if ( hit === 1 || b === 1 ) { cancel = 6; }
  }

  /* ジャンプしなかったときは、キャレット位置/選択範囲を復帰 */
  if ( cancel || isChanged ) {
    if ( pos >= 0 ) {
      s.SetActivePos( pos );
    }
    else if ( act >= 0 ) {
      s.SetAnchorPos( anc );
      s.SetActivePos( act, true );
    }
    ScrollX = sx;  ScrollY = sy;
  }

  // キャンセルフラグが 1 ~ 4(検索条件エラー)のときの所要時間の計測
  timerStatus = ( ! timerEnable ) ? ""
              : ( timerStatus )   ? timerStatus
              : TimerElapsed( new Date(), start );

  /* 終了ステータスを表示 */
  Status = (
    // メニュー表示後にジャンプしなかったときと
    // 設定を変更したときはヒット数表示のまま
    ( cancel === 5 || isChanged )
    ? Status
    : SeparateNum(
        // メニュー表示後にジャンプしたときはヒット数表示を更新
        ( cancel === 0 ) ? FindStatus()
        // 検索条件エラーでメニューを表示しなかったとき
      : ( cancel === 1 ) ? " 検索文字列がありません ( 空行 ); "
      : ( cancel === 2 ) ? " 無効な検索文字列 ( 改行 ); "
      : ( cancel === 3 ) ? " 無効な検索文字列 ( 折り返し ); "
      : ( cancel === 4 ) ? " 無効な検索文字列 ( 非・英単語 ); "
      : ( cancel === 6 ) ? " ヒットなし。 "
      :  /*  else  */      " 無効な検索文字列。 "	// ← 使われないはず
    ) + lineStatus + timerStatus
  );

}


// ---------- ▼ 関数 ▼ ---------- //

/**
 * 関数 SeparateNum( num )
 * 数値を3ケタ区切りに
 */
function SeparateNum( num ) {
  var num = ( num === 0 || ! num ) ? "0" : num;
  var a = num.toString().split( /\b/ );
  for ( var i = 0, len = a.length; i < len; i ++ ) {
    if ( a[i] === "." ) { i ++; continue; }
    if ( /\D/.test( a[i] ) ) { continue; }
    a[i] = a[i].replace( /(\d)(?=(?:\d{3})+(?!\d))/g, "$1," );
  }
  return a.join( "" );
}


/**
 * 関数 Labeling( strLine, word, menuWidth, toWideWidth, blankChar, regexpBlankChar )
 * ポップアップメニューに表示する文字列の整形
 * 
 * 文字列検索では ...
 * ・行頭空白を除去、空白文字を圧縮
 * ・削られてしまう 「&」 を補完
 * ・「¥」(U+005C) を 「∖」(U+2216) に置換:     または 「╲」(U+2572), 「﹨」 (U+FE68)
 * ・ゼロ幅や特殊な空白文字を豆腐に置換:    → 「⊠」(U+22A0) または 「▯」(U+25AF)
 * ・判別しづらい半角記号を全角に置換:      !"%'(),.:;@[]`{|}
 *     + 「a-z」 を全角に置換
 *   または半角記号の前後にスキマをつける:  HAIR SPACE「 」(U+200A)
 * ・文字数を切り詰め
 * 
 * 空行/空白行/空白文字の検索では ...
 * ・"( 空行 )" や "( 空白行 n 文字)" に置換
 */
function Labeling( line, word, menuWidth, toWideWidth, b1, regBlankChar ) {
  if ( ! b1 ) {
    return ( ! line.length ) ? "( 空行 )"
         : ( line === word ) ? "( 空白行 " + line.length + " 文字 * )"
            /*   else   */   : "( 空白行 " + line.length + " 文字 )";
  }
  var keyWidth, label;
  keyWidth = ( /^[\s ]*[\s!-~\u200A\u2216\u22A0\u25AF]+$/.test( line ) )
           ? Math.floor( menuWidth * 1.2 ) : menuWidth;
  label = line.replace( /^[\t  ]+/, "" )
              .replace( /(?:\t|[ ]{3,}|[ ]{2,})+/g, " \u203A " )  //「 › 」
              .slice( 0, keyWidth + 1 )
              .replace( /&/g, "&&" )
              .replace( /\\/g, "\u2216" )           //「∖」
              .replace( regBlankChar, "\u25AF" );   //「▯」
  if ( toWideWidth === 2 ) {
    label = label.replace( /[!"%'(),.:;@\[\]`a-z{|}]/g, function( tmp ) {
      return String.fromCharCode( tmp.charCodeAt( 0 ) + 0xFEE0 )
    } );
  }
  else if ( toWideWidth === 1 ) {
    // スラッシュ 以外の ascii 記号と fijl に HAIR SPACE (\u200A) を付加
    // [!"#$%'()*+,-.:;<=>?@\[\]^_`{|}~fijl¥⊠▯] + "&&"
    //「¥」(\u00A5),「⊠」(\u22A0),「▯」(\u25AF)
    label = label.replace( /[!-%'-.:-@\[-`{-~fijl\u00A5\u22A0\u25AF]|&&/g,
                  "\u200A$&\u200A" )
                 .replace( /\u200A+/g, "\u200A" )	// HAIR SPACE (\u200A)
                 .replace( RegExp( b1 + "\u200A", "g" ), b1 );
    // HAIR SPACE (\u200A),「›」(\u203A)
    keyWidth += ( label.match( /[\u200A\u203A]|&&/g ) || "" ).length;
  }
  label = ( label.length > keyWidth )
        ? label.slice( 0, keyWidth ) + "..." : label;
  return label;
}


/**
 * 関数 SearchCloseBrc( line, word )
 * 閉じていないカッコがないかを簡易的にチェックする
 * (閉じていなければ false )
 */
function SearchCloseBrc( line, word ) {
  var str  = line.substr( line.search( /\b(?:var |let |const )/ ) ,
             line.search( RegExp( "\\b" + word + "\\b", "" ) ) );
  var oBrc = str.match( /\(/g );	// 開きカッコの数
  var cBrc = str.match (/\)/g );	// 閉じカッコの数
  return ( ! cBrc || oBrc.length > cBrc.length ) ? 0 : 1;
}


/**
 * 関数 JsMarking( line, word, wordQuoted )
 * 編集モードが "JavaScript" か "MeryMacroJS" のとき
 * ポップアップメニューの右側に表示するマーキング
 */
function JsMarking( line, st, stQuote ) {
  var jsCheck = "";
  if ( /^\s*[$]?[_a-z][\w]*\s*$/i.test( st ) ) {
    // 検索文字列の先頭/末尾に半角空白があるときは除去する
    stQuote = stQuote.replace( /^\s+|\s+$/g, "" );
    jsCheck = (

      // 関数の定義の行: "function st()"...		[$]?
      RegExp( "\\bfunction \\s*" + stQuote + "(?=[\\s(])"	// ※)※
      + "|" + "\\b(?:var |let |const |\.prototype\.)\\s*"
      + stQuote + "\\s*=\\s*[(]?\\s*function\\b"	// ※)※
      , "" ).test( line )
      ? "\t ← fn" :

      // 変数の宣言の行: "var st"... / "var hoge = piyo, st"...	[$]?
      RegExp( "\\b(?:var |let |const )(?:\\s*|[^(]+[,]\\s*)"	// ※)※
      + stQuote + "(?=\\s*(?:[=,;]|$))"
      + "|" + "(?:^|[{.,])\\s*" + stQuote + "(?=\\s*[:]\\s*)"	// ※}※
      , "" ).test( line )
      ? "\t ← var" :

      // 変数の宣言の行: "var hoge = fuga(), st"...		[$]?
      ( RegExp( "\\b(?:var |let |const ).*?\\(.*?[,]\\s*"	// ※)※
        + stQuote + "(?=\\s*(?:[=,;]|$))"
        , "" ).test( line )
        && SearchCloseBrc( line, stQuote ) )	// 閉じていないカッコのチェック
      ? "\t ← var" :

      // 変数に値を代入している行: "st = fuga"
      RegExp( "(^|[^$]\\b(?!\\$)|[^$\\w])" + stQuote 
      + "(?=\\s*(?:=(?!=)|[-+]{2}|[-+*/%]=)(?![-+*/%=]))"
      , "" ).test( line )
      ? "\t ← sub"
      : ""
    );
  }
  return jsCheck;
}


/**
 * 関数 AddCancelLines( objPopupMenu, id, total, step, indent, menuScrollEnable )
 * 10 件ごとにセパレータと
 * 30* 件ごとに「キャンセル」行を追加する
 * ※Space キーでキャンセル可(またはスクロール)
 * 
 * 第1引数は ポップアップメニューオブジェクト
 * 第2引数は 呼び出し元の for() 文の「 i 」
 * 第3引数は 呼び出し元の for() 文の Array.length
 * 第4引数は 「キャンセル」行を表示する間隔*
 * 第5引数は 「キャンセル」の前の字下げ用の空白文字列
 * 第6引数は 「キャンセル」行の挿入許可(false ならセパレータのみ)
 */
function AddCancelLines( objMenu, id, total, step, indent, menuScrollEnable ) {
  id += 1;
  // 30件(step)ごとに「キャンセル」行を追加
  if ( menuScrollEnable && id % step === 0 || id === total ) {
    objMenu.Add( "", 0, meMenuSeparator );
    objMenu.Add( indent + " キャンセル\t& ", 0 );
  }
  // 10件ごとにセパレータを追加
  if ( id % 10 === 0 && id !== total ) {
    objMenu.Add( "", 0, meMenuSeparator );
  }
}

/**
 * 関数 DevidedMenu( objPopupMenu, itemsArray, itemsCount, itemsWidth, subMenuStyle, subMenuHeight, blankChar )
 * サブメニュー「※ 30* 件ずつ表示 ※  ▶」/「1 - 30* 件目  ▶」に
 * 配列の「アイテム」を分割表示する
 * ※「30*」の部分の数値は設定用変数 subMenuHeight で指定
 */
function DevidedMenu( objPopupMenu, mArray, mCount, numWidth, subMenuStyle, subMenuHeight, b1 ) { 
  b1 = ( b1.length === 1 ) ? b1 : " ";
  var devidedSubMenu = CreatePopupMenu();
  var indent = "".PadSpace( numWidth, b1 );
  var smArray = [];		// SubMenu object Array
  var tickedId;

  if ( subMenuStyle ) {
    // 「※ 30* 件ずつ表示 ※  ▶」をピン止め
    objPopupMenu.AddPopup( "※".PadSpace( numWidth - 1, b1 ) + " "
      + subMenuHeight + " 件ずつに分割して表示 (&D) ※"
    , devidedSubMenu );
    objPopupMenu.Add( "", 0, meMenuSeparator );
  }

  // 配列 mArray のアイテムをサブメニューに追加していく
  mArrayLoop:
  for ( var i = 0, j = 1, k = subMenuHeight,
   subMenu, line, num; i < mCount; i ++, j ++, k ++ ) {
    if ( i % subMenuHeight === 0 ) {
      // smArray に「_from - _to 件目  ▶」用のサブメニューオブジェクトを生成する
      subMenu = CreatePopupMenu();
      smArray.push( {
        menu: subMenu,
        from: j,
        to: ( k < mCount ) ? k : mCount
      } );
    }

    // mArray のアイテムをサブメニューオブジェクト配下に追加する
    // ※ なぜか smArray[smArray.length -1].menu ではなく subMenu への Add() でよい
    line  = mArray[i];
    num = line.id.PadSpace( numWidth, b1 ).replace( /\d$/, "&$&: " );
    subMenu.Add( num + line.str, line.id, line.flag );
    // アクティブ行のチェックマークフラグのついた行
    if ( line.flag === 1 ) { tickedId = smArray.length - 1; }

    // 10 件ごとにセパレータ、subMenuHeight ごとに「キャセル」行
    if ( j % 10 === 0 || j === mCount) {
      subMenu.Add( "", 0, meMenuSeparator );
      if ( j % subMenuHeight === 0 || j === mCount ) {
        subMenu.Add( indent + " キャンセル\t& ", 0 );
      }
    }
  }	// end: for ()

  // 「_from - _to 件目  ▶」メニュー項目を親メニュー内に追加
  var popupMenu = subMenuStyle ? devidedSubMenu : objPopupMenu;
  var mWidth    = mCount.toString().length;
  var smIndent  = subMenuStyle ?  "" : indent;

  smArrayLoop:
  for ( var i = 0, smCount = smArray.length, tick, smLabel;
  i < smCount; i ++ ) {
    // ※ AddPopup に meMenuChecked は付けられないので文字「*」で代替
    tick = ( i === tickedId ) ? "* " : b1 + " ";
    smLabel = smArray[i].from.PadSpace( mWidth, b1 ).replace( /\d/, "&$&" )
            + " - "
            + smArray[i].to.PadSpace( mWidth, b1 ) + " 件目:";
    // ※ smArray[i].menu は PopupMenu オブジェクト
    popupMenu.AddPopup( smIndent + tick + smLabel, smArray[i].menu );

    // セパレータと「キャセル」行
    if ( ( i % subMenuHeight === 0 && i !== 0 ) || i === smCount - 1 ) {
      popupMenu.Add( "", 0, meMenuSeparator );
      if ( subMenuStyle ) {
        popupMenu.Add( " キャンセル\t& ", 0 );
      }
    }
    if ( i % 10 === 0 && i !== 0 ) {
      popupMenu.Add( "", 0, meMenuSeparator );
    }
  }
  return popupMenu;
}


/**
 * 関数 GetIniOptionNum( key )
 * 引数で指定された設定項目の「値」を返す(※数値のみ)
 */
function GetIniOptionNum( key ) {
  var Fso = new ActiveXObject( "Scripting.FileSystemObject" );
  // Mery.ini を探す
  var meryPath = editor.FullName;
  var iniPath  = meryPath.replace( /\.exe$/i, ".ini" );
  if ( ! Fso.FileExists( iniPath ) ) {
    var mery = Fso.GetBaseName( meryPath );
    iniPath = new ActiveXObject( "WScript.Shell" )
              .ExpandEnvironmentStrings( "%APPDATA%" )
            + "\\Mery\\" + mery + ".ini";
  }
  // Mery.ini を読み込む
  var iniFile = Fso.OpenTextFile( iniPath, 1 );
  var iniText = iniFile.ReadAll();
  iniFile.Close();
  // 項目の値を取得する
  return + RegExp( "^" + key + "=(\\d+)$", "m" ).exec( iniText )[1];
}


/**
 * 関数 SettingsMenu( objSettings, menuIndent )
 * ポップアップメニューに設定変更用のサブメニューを表示する
 */
function SettingsMenu( setting, menuIndent ) {
  var ss = setting;
  // ※定数 meMenuChecked の値は 1 なので ***Enable *1 で代用できる
  var grayFlag      = ! ss.settingEnable ? meMenuGrayed : 0;
  var historyFlag   = ( ss.historyDisable || ! ss.settingEnable )
                    ? meMenuGrayed : 0;
  var highlightFlag = ( ss.highlightEnable && ! ss.historyDisable )
                    ? meMenuChecked : 0;
  var autoLineFlag  = ( ss.autoLineModeEnable || ! ss.settingEnable )
                    ? meMenuGrayed : 0;
  var submenuFlag   = ss.subMenuEnable ? meMenuChecked : 0;
  var smStyleFlag0  = ss.subMenuStyle ? 0 : meMenuChecked;
  var smStyleFlag1  = ss.subMenuStyle ? meMenuChecked : 0;
  var smGrayFlag    = ( ! ss.subMenuEnable || ! ss.settingEnable )
                    ? meMenuGrayed : 0;
  var omitItemFlag  = ( ( ! ss.settingEnable || ! ss.subMenuEnable )
                        ? meMenuGrayed : 0 )
                    + ( ( ss.omitEnable && ss.subMenuEnable )
                        ? meMenuChecked : 0 );
  var maxHeightFlag = ( ! ss.settingEnable || ss.subMenuHeight >= 100 )
                    ? meMenuGrayed : 0;
  var minHeightFlag = ( ! ss.settingEnable || ss.subMenuHeight < 20 )
                    ? meMenuGrayed : 0;
  var maxWidthFlag  = ss.menuWidth > 100 ? meMenuGrayed : 0;
  var minWidthFlag  = ss.menuWidth < 30 ? meMenuGrayed : 0;
  var toWideFlag0   = ( ss.toWideWidth === 0 ) ? meMenuChecked : 0;
  var toWideFlag1   = ( ss.toWideWidth === 1 ) ? meMenuChecked : 0;
  var toWideFlag2   = ( ss.toWideWidth === 2 ) ? meMenuChecked : 0;

  var setMenu = CreatePopupMenu();
  setMenu.Add( "「大文字/小文字」 を区別する (&C)"
             , 10000001, ss.caseEnable + grayFlag );
  setMenu.Add( "「英単語のみ」 検索する (&W)"
             , 10000002, ss.onlyWordEnable + grayFlag );
  setMenu.Add( "「空行/空白行」 を検索対象にする (&B)"
             , 10000003, ss.blankLineEnable + grayFlag );
  setMenu.Add( "", 0, meMenuSeparator );
  setMenu.Add( "「検索履歴」 に残さない (&H)"
             , 10000004, ss.historyDisable + grayFlag );
  setMenu.Add( "「検索文字列の強調表示」 を残す (&F)"
             , 10000005, highlightFlag + historyFlag );
  setMenu.Add( "", 0, meMenuSeparator );
  setMenu.Add( "「行の表示方法」 を自動設定する (&A)"
             , 10000006, ss.autoLineModeEnable + grayFlag );
  setMenu.Add( "・ 行数を 「論理行」 で表示する"
               + ( ss.logical ? " (&L)" : "" )
             , 10000007, ss.logical + autoLineFlag );
  setMenu.Add( "・ 行数を 「表示行」 で表示する"
               + ( ss.logical ? "" : " (&V)" )
             , 10000007, ! ss.logical + autoLineFlag );
  setMenu.Add( "", 0, meMenuSeparator );
  setMenu.Add( "「検索処理の所要時間」 を表示する (&T)"
             , 10000008, ss.timerEnable + grayFlag );
  setMenu.Add( "「ヒット件数」(出現総数)を表示する (&G)"
             , 10000009, ss.countEnable + grayFlag );
  setMenu.Add( "", 0, meMenuSeparator );
  setMenu.Add( "「分割サブメニュー」 を表示する (&D)"
             , 10000010, submenuFlag + grayFlag );
  setMenu.Add( "・ メインメニューの中に「 1 - " + ss.subMenuHeight 
               + " 件目: \u25B6」 を表示する"
               + ( ss.subMenuStyle ? " (&E)" : "" )
             , 10000011, smStyleFlag0 + smGrayFlag );
  setMenu.Add( "・ 「" + ss.subMenuHeight
               + " 件ずつに分割して表示 \u25B6」"
               + " の中に表示する" + ( ss.subMenuStyle ? "" : " (&E)" )
             , 10000011, smStyleFlag1 + smGrayFlag );
  setMenu.Add( "  サブメニューの分割単位を 10行 増やす (&X) \t"
               + ss.subMenuHeight + ( ss.subMenuHeight >= 100
               ? "" : " → " + ( ss.subMenuHeight + 10 ) )
             , 10000012, maxHeightFlag );
  setMenu.Add( "  サブメニューの分割単位を 10行 減らす (&Z) \t"
               + ss.subMenuHeight + ( ss.subMenuHeight < 20
               ? "" : " → " + ( ss.subMenuHeight - 10 ) )
             , 10000013, minHeightFlag );
  setMenu.Add( "  ▲ 「キャンセル」 の挿入間隔と兼用  ▲"
             , 0, meMenuGrayed );
  setMenu.Add( "  メインメニューにすべてのアイテムを表示しない (&U)"
             , 10000014, omitItemFlag );
  setMenu.Add( "", 0, meMenuSeparator );
  setMenu.Add( "  " + ss.subMenuHeight
               + " 件ごとに 「キャンセル」 を表示する (&K)"
             , 10000015, ss.menuScrollEnable + grayFlag );
  setMenu.Add( "", 0, meMenuSeparator );
  setMenu.Add( "  メニューの表示幅を 10文字 増やす (&P) \t"
               + ss.menuWidth + ( ss.menuWidth > 100
               ? "" : " → " + ( ss.menuWidth + 10 ) )
             , 10000016, maxWidthFlag + grayFlag );
  setMenu.Add( "  メニューの表示幅を 10文字 減らす (&M) \t"
               + ss.menuWidth + ( ss.menuWidth < 30
               ? "" : " → " + ( ss.menuWidth - 10 ) )
             , 10000017, minWidthFlag + grayFlag );
  setMenu.Add( "", 0, meMenuSeparator );
  setMenu.Add( "・ 半角 a-z と記号を全角で表示する"
               + ( toWideFlag2 ? "" : " (&F)" )
             , 10000018, toWideFlag2 + grayFlag );
  setMenu.Add( "・ 半角英・記号の文字間隔を調整する"
               + ( toWideFlag1 ? "" : " (&F)" )
             , 10000019, toWideFlag1 + grayFlag );
  setMenu.Add( "・ 半角英数記号を半角のまま表示する"
               + ( toWideFlag0 ? "" : " (&F)" )
             , 10000020, toWideFlag0 + grayFlag );
  setMenu.Add( "", 0, meMenuSeparator );
  setMenu.Add( "・ マウスポインタの位置 にメニューを表示する"
               + ( ss.menuPosMouse ? "" : " (&M)" )
             , 10000021, ss.menuPosMouse + grayFlag );
  setMenu.Add( "・ 入力カーソル の位置 にメニューを表示する"
               + ( ss.menuPosMouse ? " (&M)" : "" )
             , 10000021, ! ss.menuPosMouse + grayFlag );
  setMenu.Add( "", 0, meMenuSeparator );
  setMenu.Add( "", 0, meMenuSeparator );
  setMenu.Add( "  設定内容をロックする (&S)"
             , 10000000, ! ss.settingEnable * 1 );
  setMenu.Add( "", 0, meMenuSeparator );
  setMenu.Add( "「ジャンプ」マクロの JS ファイルを開く (&O)"
             , 10000100 );
  setMenu.Add( "「ジャンプ」マクロの JSON ファイルを開く (&J)"
             , 10000101 );
  setMenu.Add( "", 0, meMenuSeparator );
  setMenu.Add( "  設定内容を初期化する (&I)", 10000102, grayFlag );
  setMenu.Add( "  キャンセル\t& ", 0 );

  return setMenu;
}


/**
 * 関数 SettingsChange( num, objSetting, jsonName ) 
 * ポップアップメニューで選択した項目の設定状態を変更する
 */
function SettingsChange( num, setting, jsonName ) {
  // setting = IO.Deserialize( setting, jsonName );
  var ss = setting;
  var sIsChanged = true;
  switch ( num ) {
  case 10000000:	// 設定内容をロックする
    ss.settingEnable = ! ss.settingEnable;
    break;
  case 10000001:	// 「大文字/小文字」 を区別する
    ss.caseEnable = ! ss.caseEnable;
    break;
  case 10000002:	// 「英単語のみ」 検索する
    ss.onlyWordEnable = ! ss.onlyWordEnable;
    break;
  case 10000003:	// 「空行/空白行」 を検索対象にする
    ss.blankLineEnable = ! ss.blankLineEnable;
    break;
  case 10000004:	// 「検索履歴」 に残さない
    ss.historyDisable = ! ss.historyDisable;
    break;
  case 10000005:	// 「検索文字列の強調表示」 を残す
    ss.highlightEnable = ! ss.highlightEnable;
    break;
  case 10000006:	// 「行の表示方法」 を自動設定する
    ss.autoLineModeEnable = ! ss.autoLineModeEnable;
    break;
  case 10000007:	// 行数を 「論理行/表示行」 で表示する
    ss.logical = ! ss.logical;
    break;
  case 10000008:	// 「検索処理の所要時間」 を表示する
    ss.timerEnable = ! ss.timerEnable;
    break;
  case 10000009:	// 「ヒット件数」(出現総数)を表示する
    ss.countEnable = ! ss.countEnable;
    break;
  case 10000010:	// 「分割サブメニュー」 を表示する
    ss.subMenuEnable = ! subMenuEnable;
    break;
  case 10000011:	// 「20* 件ずつに分割して表示 ▶」のなかに表示する
    ss.subMenuStyle = Number( ! ss.subMenuStyle );
    break;
  case 10000012:	// サブメニューの分割単位を 10行 増やす
    ss.subMenuHeight += 10;
    break;
  case 10000013:	// サブメニューの分割単位を 10行 減らす
    ss.subMenuHeight -= 10;
    break;
  case 10000014:	// メインメニューにはすべてのアイテムを表示しない
    ss.omitEnable = ! ss.omitEnable;
    break;
  case 10000015:	// 20 件* ごとに 「キャンセル」 を表示する
    ss.menuScrollEnable = ! ss.menuScrollEnable;
    break;
  case 10000016:	// メニューの表示幅を 10文字 増やす
    ss.menuWidth += 10;
    break;
  case 10000017:	// メニューの表示幅を 10文字 減らす
    ss.menuWidth -= 10;
    break;
  case 10000018:	// 半角 a-z と記号を全角で表示する
    ss.toWideWidth = 2;
    break;
  case 10000019:	// 半角英・記号の文字間隔を調整する
    ss.toWideWidth = 1;
    break;
  case 10000019:	// 半角英数記号を半角のまま表示する
    ss.toWideWidth = 0;
    break;
  case 10000021:	// 入力カーソル/マウスポインタ の位置 にメニューを表示する
    ss.menuPosMouse = ! ss.menuPosMouse;
    break;
  case 10000100:	// 「ジャンプ」マクロの JS ファイルを開く
    sIsChanged = false;
    Status = " " + ScriptFullName;
    new ActiveXObject( "WScript.Shell" )
    .Run( '"' + editor.FullName + '" "' + ScriptFullName + '"' );
    break;
  case 10000101:	// 「ジャンプ」マクロの JSON ファイルを開く
    sIsChanged = false;
    OpenJumpJson( jsonName, decode = false, ss.toWideWidth );
    break;
  case 10000102:	// 設定内容を初期化する
    sIsChanged = false;
    CheckJsonFile( jsonName, decode = false, ss.toWideWidth );
    break;
  default:
    sIsChanged = false;
    break;
  }
  if ( sIsChanged ) {
    IO.Serialize( ss, jsonName );
    Status = " 設定の変更を保存しました。";
  }
}


/**
 * 関数 OpenJumpJson( jsonName, bool )
 * JSON ファイルを開く
 * 
 * 引数: JSON ファイルのベース名
 */
function OpenJumpJson( jsonName, decode, toWideWidth ) {
  try {
    IO.Serialize( setting, jsonName );
    Sleep( 500 );
    var jsonDir = JsonDir();
    var jsonPath = jsonDir + "\\" + jsonName + ".json";
    if ( IO.Path.IsExist( jsonPath ) ) {
      var json = JsonContents( jsonPath, decode, toWideWidth );
      if ( json.length ) {
        var macroName = ScriptName.replace( /\.[^.]+$/, "" );
        var msg = "設定ファイルの内容:"
                + ( decode ? " (※デコードして表示)" : "" ) + " \n\n"
                + json + "設定ファイルを開きますか? ";
        var nSecondsToWait = 0;
        var strTitle = "Mery: JSON ファイルの確認 - 「" + macroName + "」マクロ";
        // ボタン 4:[はい][いいえ]、アイコン 32:[?]、標準ボタン 256:第2ボタン
        var nType = 4 + 32 + 256;
        var WshShell = new ActiveXObject( "WScript.Shell" );
        var msgBox = WshShell.Popup( msg, nSecondsToWait, strTitle, nType );
        if ( msgBox === 6 ) {	// 6:[はい]
          WshShell.Run( "\"" + editor.FullName + "\" \"" + jsonPath + "\"" );
        }
      }
    }
    else {
      Alert( "設定ファイルがありません \n\n" + jsonDir + "  " );
    }
  } catch( e ) { Alert( e + " \n\n" + jsonPath + "  " ); return; }
}


/** 
 * 関数 CheckJsonFile( jsonName, bool )
 * JSON ファイルの初期化/実在確認
 * ※ グローバルスコープの initialSettings を直接参照する
 * 
 * 引数: JSON ファイルのベース名
 */
function CheckJsonFile( jsonName, decode, toWideWidth ) {
  // var jsonName = jsonName || ScriptName.replace( /\.js$/i, "" );
  var jsonDir = JsonDir();
  var jsonPath = jsonDir + "\\" + jsonName + ".json";
  var macroName = ScriptName.replace( /\.[^.]+$/, "" );
  var msg = jsonPath + "  \n\n"
          + "設定ファイルを初期化しますか? "
  var nSecondsToWait = 0;
  var strTitle = "Mery: JSON ファイルの初期化 - 「" + macroName + "」マクロ";
  // ボタン 4:[はい][いいえ]、アイコン 48:[!]、標準ボタン 256:第2ボタン
  var nType = 4 + 48 + 256;
  var WshShell = new ActiveXObject( "WScript.Shell" );
  var msgBox = WshShell.Popup( msg, nSecondsToWait, strTitle, nType );
  if ( msgBox === 6 ) {	// 6:[はい]
    try {
      IO.Serialize( initialSettings, jsonName );
      Sleep( 500 );
    }
    catch( e ) { Alert( e + " \n\n" + jsonPath + "  " ); return; }
    if ( IO.Path.IsExist( jsonPath )
    && (json = JsonContents( jsonPath, decode, toWideWidth )  ).length ) {
      msg = "設定ファイルは正常です"
          + ( decode ? "(※デコードして表示)" : "" ) + " \n\n"
          + json;
      nType = 64;
      WshShell.Popup( msg, nSecondsToWait, strTitle, nType );
    }
    else {
      Alert( "設定ファイルがありません \n\n" + jsonDir + "  " );
    }
  }
}


/** 
 * 関数 JsonDir()
 * JSON ファイルの親フォルダのパス
 */
function JsonDir() {
  var jsonDir;
  if ( IO.Path.IsExist( editor.FullName.replace( /\.exe$/, ".ini" ) ) ) {
    jsonDir = editor.FullName.replace( /[^\\]+\.exe$/, "" )
            + "Macros\\MacroSettings";
  }
  else {
    jsonDir = new ActiveXObject( "WScript.Shell" )
              .SpecialFolders( "APPDATA" )
            + "\\Mery\\MacroSettings";
  }
  return jsonDir;
}


/** 
 * 関数 JsonContents( jsonPath [, bool] )
 * JSON ファイルの文字列をメッセージボックス用に整形する
 * 
 * 第1引数は JSON ファイルのパス
 * 第2引数は真偽値(省略可):  true なら \uHHHH をデコードする
 */
function JsonContents( jsonPath, decode, toWideWidth ) {
  var json = IO.LoadFromFile( jsonPath )
             .replace( /^\{/, "\n{\n  " )
             .replace( /\}$/g, "\n}\n\n" )
             .replace( /,/g, " ,\n  " )
             .replace( /\":/g, "\": " );
  // 「テキスト整形」マクロより >> 符号化/復号化 >> \uHHHH デコード
  if ( decode ) {
    json = json.replace( /\\u([0-9A-Fa-f]{4})/g, function( s, n ) {
      return String.fromCharCode( Number( "0x" + n ) );
    } );
  }
  // 半角アルファベットの小文字を全角にするなら
  if ( toWideWidth ) {
    json = json.replace( /[!"%'(),.:;@\[\]`a-z{|}]/g, function( tmp ) {
      return String.fromCharCode( tmp.charCodeAt( 0 ) + 0xFEE0 );
    } );
  }
  return jsonPath + "  \n" + json;
}


メモ

おもな更新履歴

  • (2018/11/12 sukemaru)
・新規アップロード(sukemaru版の【通常版】)
  • (2018/11/25 sukemaru)
・【人柱版】を新規アップロード
  • (2018/12/13 sukemaru)
・【通常版】を更新(【人柱版】から追加機能の一部を逆移植)
・【人柱版】を更新(機能追加)
  • (2018/12/16 sukemaru)
・【include版】を新規アップロード
・【人柱版】を修正
  • (2019/03/13 sukemaru)
・【include版】を修正・更新
  • (2019/04/07 - 2019/04/16 sukemaru)
・Quit メソッドを削除
・【include版】を修正・更新
・おまけマクロを修正・更新
  • (2020/06/25 sukemaru)
・【通常版】を修正・更新
・【include版】を更新
・【人柱版】を廃止( ⇒ 詳細設定は include 版に一本化)
・おまけマクロから「次/前の文字列を検索」を削除( ⇒ 「次/前の文字列を検索・改」)

行の表示方法が「表示座標」のとき、折り返し部分にかかった文字列にはマッチしないままです。
実装すると動作速度が遅くなり、実用に適さなくなってしまいます…

スポンサーリンク