「クリップボード履歴」メニューのマクロ化

提供: MeryWiki
移動先: 案内検索


公式フォーラム[1] にて「クリップボード履歴」メニューの将来的に廃止されることが 予告?検討? されていますが、後継機能としては 「クリップボード履歴」プラグイン の登場により今後も安泰のようですね。

ということで、試験運用中だった自家用マクロを再度リサイクルして「クリップボード履歴スニペット」の統合版ポップアップメニューにしてみました。

「クリップボード履歴」機能は Mery 2.8.1 以降の『延命措置』の新パラメータ[2] を利用したもの。
「スニペット」機能は スニペットプラグイン の設定ファイル snippets.txt を読み書きするというかたちにしています。


  • あらかじめ「includeライブラリ」の導入が必要です。
  • スニペットプラグイン」を導入していないでも「スニペット(定型文)」機能を利用できます。
  • 外部実行ファイル「GetKeyState.exe 」で機能を拡張できます。
  • このマクロは Mery 2.8.1 以降でしか利用できません。
  • 動作確認は Windows XP sp3 (32bit) × Mery ベータ版 2.8.2 - 2.8.3 (ポータブル) でしかしていません。


一見して 誰得? なマクロですが、「クリップボード履歴」を「スニペット」機能とまとめてツールバーアイコン化できるという素敵なメリットがあります。

cf. マクロライブラリには「定型文を挿入」機能に特化したマクロも別途あります。


更新履歴[編集]

• 2019/08/01: 初版
• 2019/08/06:
 ・「ピン止めアイテムを貼り付け」コードの変数ミスを修正
 ・クリップボード履歴内の重複アイテムを削除する設定を追加。
 ・ピン止めアイテムが少ないときはメインメニューにアイテムを表示。
 ・snippets.txt 内の空行を無視。
 ・クリップボード履歴に長大なデータがあるときの動作速度を少しだけ改善。

ソースコード[編集]

ダウンロード: 「ファイル:クリップボード履歴.zip」(アイコン入り)
#title = "クリップボード履歴..."
#tooltip = "クリップボード履歴 と スニペット"
#include "include/IO.js"
// #icon = "clipboard_history[1].ico"

/**
 * --------------------------------------------------
 * 「クリップボード履歴 と スニペット」マクロ
 * 	sukemaru, 2019/08/01 - 2019/08/05
 * --------------------------------------------------
 * 「クリップボード履歴」メニューと「スニペット」プラグインと同等の機能を
 *   ひとつのポップアップメニューに統合します。
 * 
 * 【このマクロの仕様・制限事項】
 * ・ver 2.8.0 以前の Mery では使用できません。
 * ・「クリップボード履歴」は Mery 本体のメモリストアのものを使用します。
 * ・「スニペット」プラグインの設定ファイル snippets.txt の読み書きをします。
 * ・「include ライブラリ」が必要です(snippets.txt ファイルの読み書きに使用)。
 * ・「GetKeyState.exe(キー状態取得実行ファイル) 」で機能を拡張できます。
 *      → クリップボード履歴のアイテムを Ctrl+クリックした場合、
 *         アイテムを貼り付けし、貼り終えたアイテムを削除します。
 * 
 * ※「スニペット」プラグインを導入していない場合でも、
 *   「ピン止めアイテム/スニペット」機能を使用できます。
 *     snippets.txt ファイルの保存場所は Mery\Plugins\Snippets フォルダです
 *   (インストーラ版では %AppData%\Mery\Plugins\Snippets フォルダ)。
 * 
 * ※「GetKeyState.exe」は Mery\Macros フォルダに配置してください
 *   (Macros フォルダ以外の場所にあるなら設定項目でパスを指定する)。
 * ※「GetKeyState.exe」を導入していない場合は、
 *   「貼り付けしてからアイテムを削除する」サブメニューを表示します。
 * 
 * (2019/08/05)
 * ・「ピン止めアイテムを貼り付け」のコードミスを修正
 * ・クリップボード履歴内の重複アイテムを削除する設定を追加。
 * ・ピン止めアイテムが少ないときはメインメニューにアイテムを表示。
 * ・snippets.txt 内の空行を無視。
 * ・クリップボード履歴に長大なデータがあるときの動作速度を少しだけ改善。
 */

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

// ■ クリップボード履歴内の重複アイテムを削除する
var delDupEnable = true;	// true: 削除する / false: 削除しない

// ■ ピン止めアイテムをサブメニュー化するアイテム数の閾値
var snSubMenu = 5;	// これよりアイテム数が多いときはサブメニューで表示する

// ■ ポップアップメニューを表示する位置
var menuPosMouse = true;	// true: マウス位置 / false: キャレット位置

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

// ■ GetKeyState.exe のフルパスを指定する場合(「\」記号はふたつがさね「\\」で)
var getKeyStatePath = "";	// editor.FullName.replace( /[^\\]+$/, "Macros\\GetKeyState.exe" );

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


var Fso = new ActiveXObject( "Scripting.FileSystemObject" );
var WshShell = new ActiveXObject( "WScript.Shell" );

// ClipboardData。Get/ClearData() に引数を指定できるのは ver 2.8.1 以降
if ( ! VersionCheck( "2.8.1" ) ) {
  WshShell.SendKeys( "%TC" );
}

else {
  var meryPath = editor.FullName;
  var mery = Fso.GetBaseName( meryPath );
  var meryDir = Fso.GetParentFolderName( meryPath ) + "\\";
  var profileDir = meryDir;
  var iniPath = meryDir + mery + ".ini";
  if ( ! Fso.FileExists( iniPath ) ) {
    profileDir = WshShell.SpecialFolders( "APPDATA" ) + "\\Mery\\";
  }
  var snippetsPath = profileDir + "Plugins\\Snippets\\Snippets.txt";
  var snIsExist = Fso.FileExists( snippetsPath );
  var getKeyStatePath;
  var getKeyState = getKeyStatePath || meryDir + "Macros\\GetKeyState.exe";
  var gksIsExist = Fso.FileExists( getKeyState );
  var $ctrl = 0;

  var d = editor.ActiveDocument;
  var s = d.selection;
  var st = s.Text;

  // ピン止めアイテム (スニペット)
  var snippets = "", snCount = 0;
  if ( snIsExist ) {
    snippets = IO.LoadFromFile( snippetsPath, "utf-8" ) || "";
    var snArray = snippets.replace( /^\n/gm, "" ).replace( /\n$/, "" )
                          .split( "\n" );
    snCount = snArray.length;
    var sn = ( snCount > snSubMenu );
  }

  // クリップボード履歴
  var cb = ClipboardData;
  var cbArray = [];
  for ( var i = 0; ; i ++ ) {
    cbItem = cb.GetData( i ) || "";
    if ( ! cbItem ) { break; }
    // 重複するアイテムを履歴から削除する
    if ( delDupEnable ) {
      for ( var j = i + 1, cbNextItem; ; j ++ ) {
        cbNextItem = cb.GetData( j ) || "";
        if ( ! cbNextItem ) { break; }
        if ( cbItem == cbNextItem ) {
          cb.ClearData( j );
          j --;
        }
      }
    }
    // 履歴アイテムを配列に収納する
    cbArray.push( cbItem );
  }
  var cbCount = cbArray.length;
  var width = String( Math.max( snCount, cbCount ) ).length;

  // ポップアップメニューの準備
  var menu = CreatePopupMenu();
  var sm0 = CreatePopupMenu();
  var sm1 = CreatePopupMenu();
  var sm2 = CreatePopupMenu();
  var menuFlags = d.ReadOnly ? meMenuGrayed : 0;

  var t = ( sn || snArray[0] == "" ) ? "" : "▼ ";
  menu.AddPopup( t + "ピン止めアイテム/スニペット (&P) " + t, sm0 );

  // スニペットのアイテムをメニューに
  if ( snCount ) {
    var snMenu = sn ? sm0 : menu;
    for ( var i = 0, id, label; i < snCount; i ++ ) {
      id = i + 1;
      label = snArray[i].replace( /\t[\s\S]*/, "" );
      if ( label ) {
        snMenu.Add( MenuKey( label, id, width ), id + 300, menuFlags );
      }
    }
    if ( sn ) {
      snMenu.Add( "", 0, meMenuSeparator );
    }
  }
  if ( st ) {
    sm0.Add( "選択範囲を登録 (&P)", 300 );
  }
  if ( snIsExist ) {
    sm0.Add( "スニペットを編集 (&E) ...", 400 );
  }
  sm0.Add( "", 0, meMenuSeparator );
  sm0.Add( "キャンセル & ", 0 );

  // クリップボード履歴のアイテムをメニューに
  if ( cbCount ) {
    menu.Add( "", 0, meMenuSeparator );
    menu.Add( "▼ クリップボード履歴 ▼", 0, meMenuGrayed );
    for ( var i = 0, id, label; i < cbCount; i ++ ) {
      id = i + 1;
      label = MenuKey( cbArray[i], id, width );
      menu.Add( label, id, menuFlags );
      sm1.Add( label, id + 100, menuFlags );
      sm2.Add( label, id + 200 );
    }
    menu.Add( "", 0, meMenuSeparator );

    if ( ! gksIsExist ) {
      menu.AddPopup( "貼り付けしてからアイテムを削除する (&M)", sm1 );
    }
    menu.AddPopup( "クリップボードの履歴からアイテムを削除する (&D)", sm2 );
    // menu.Add( "", 0, meMenuSeparator );
    menu.Add( "クリップボードのすべての履歴を削除する (&C)", 500 );
  }

  menu.Add( "", 0, meMenuSeparator );
  menu.Add( "キャンセル & ", 0 );


  // ポップアップメニューを表示
  var r = menu.Track( + menuPosMouse );
  var confirmStr = menu.GetText( r ).replace( /する \(&\w\)$/, "しますか? ");

  if ( r == 0 ) {
    ;
  }

  // 1 ~ 16: 貼り付け
  else if ( r < 100 ) {
    d.Write( cbArray[ r -1 ] );
    // Ctrl キーを押しながらのときは、貼り付けたアイテムを履歴から削除する
    if ( gksIsExist ) {
      $ctrl = WshShell.Run( "\"" + getKeyState + "\" c", 0, true );
    }
    if ( $ctrl == 1 ) {
      cb.ClearData( r -1 );
    }
  }

  // 101 ~ 116: 貼り付けして、そのアイテムをクリップボード履歴から削除する
  else if ( r > 100 && r < 200 ) {
    d.Write( cbArray[ r -101 ] );
    cb.ClearData( r -101 );
  }

  // 201 ~ 216: アイテムをクリップボード履歴から削除する
  else if ( r > 200 && r < 300 ) {
    cb.ClearData( r -201 );
  }

  // 301 ~ 399: ピン止めアイテムを貼り付け
  else if ( r > 300 && r < 400 ) {
    var str = snArray[ r -301 ].replace( /^[^\t]*\t/, "" )
                               .replace( /(\\r)?\\n/g, "\n" )
                               .replace( /\\t/g, "\t" )
    d.Write( str );
  }

  // 300: 選択範囲を登録(ピン止め)
  else if ( r == 300 ) {
    var snippetsDir = Fso.GetParentFolderName( snippetsPath );
    var pluginsDir = Fso.GetParentFolderName( snippetsDir );
    if ( ! Fso.FolderExists( pluginsDir ) ) {
      Fso.CreateFolder( pluginsDir );
    }
    if ( ! Fso.FolderExists( snippetsDir ) ) {
      Fso.CreateFolder( snippetsDir );
    }
    var str = snippets
            + ( snippets ? "\n" : "" )
            + st.replace( /\t/g, "\\t" )
                .replace( /\r?\n/g, "\\n" );
    IO.SaveToFile( snippetsPath, str, "utf-8", true );
  }

  // 400: ピン止めアイテムを編集(snippets.txt を開く)
  else if ( r == 400 ) {
    WshShell.Run( "\"" + editor.FullName + "\" \"" + snippetsPath + "\"" )
  }

  // 500: クリップボード履歴をすべて削除する
  else if ( r == 500 && Confirm( confirmStr ) ) {
    for ( var i = 0; i < cbCount; i ++ ) {
      cb.ClearData( i );
    }
  }

}


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

/**
 * 関数 VersionCheck( versionStr )
 * Mery 本体が引数で指定したバージョン以上かチェックする( i.e. "2.6.9" )
 * 戻り値は、真偽値 true/false
 */
function VersionCheck( versionStr ) {
  var Pad2 = function( str ) {
    return str.replace( /[0-9]+/g , function( digit ) {
      return digit.length < 2 ? "0" + digit : digit
    } )
  };
  var editorVer = + ( Pad2( editor.Version ).replace( /\./g, "" ).slice( 0, 6 ) );
  var requirement = + ( Pad2( versionStr ).replace( /\./g, "" ).slice( 0, 6 ) );
  return ( editorVer >= requirement );
}

/**
 * 関数 MenuKey( str, num, width )
 * ポップアップメニューに表示するラベルを生成する
 * ・行頭空白を除去、空白文字を圧縮
 * ・削られてしまう「&」を補完
 * ・「¥」(U+005C) を「∖」に置換:「∖」(U+2216) または「╲」(U+2572) 「﹨」(U+FE68)
 * ・判別しづらいメタ文字を全角に置換:	!"%'(),.:;@[]`{|}
 * ・「a-z」を全角に置換
 * ・行番号を空白でケタ埋め:	EN SPACE「 」(U+2002)
 * ・文字数を切り詰め
 */
function MenuKey( str, num, width ) {
  var menuKey = str.replace( /^[\t  ]+/gm, " › " )
                   .replace( /\r?\n/g, "⏎ " )
                   .replace( /(?:\t|[ ]{3,}|[ ]{2,})+/g, " › " )
                   .replace( /[&]/g, "&&" )
                   .slice( 0, menuWidth + 1 )	// (2019/08/05 追加)
                   .replace( /[\\]/g, "∖" )
                   .replace( /([^∖!"',.:;`|\t  ])([∖]+)/g, "$1 $2" )
                   .replace( /[!"%'(),.:;@\[\]`a-z{|}]/g,
                     function( tmp ) {
                       return String.fromCharCode( tmp.charCodeAt( 0 ) + 0xFEE0 )
                     } );
  num = ( "   " + num ).slice( - width ).replace( /\d$/, "&$&" );
  menuKey = ( menuKey.length > menuWidth )
          ? menuKey.slice( 0, menuWidth ) + " ..."
          : menuKey;
  return num + ": " + menuKey;
}
スポンサーリンク