「クリップボード履歴」メニューのマクロ化
公式フォーラム[1] にて「クリップボード履歴」メニューの将来的に廃止されることが 予告?検討? されていますが、後継機能としては 「クリップボード履歴」プラグイン の登場により今後も安泰のようですね。
ということで、試験運用中だった自家用マクロを再度リサイクルして「クリップボード履歴とスニペット」の統合版ポップアップメニューにしてみました。
- 「クリップボード履歴」機能は Mery 2.8.1 以降の『延命措置』の新パラメータ[2] を利用したもの。
- 「スニペット」機能は スニペットプラグイン の設定ファイル snippets.txt を読み書きするというかたちにしています。
- このマクロは Mery 2.8.1 以降でしか利用できません。
- あらかじめ「includeライブラリ」の導入が必要です。
- 外部実行ファイル「GetKeyState.exe 」で機能を拡張できます。
- 「スニペットプラグイン」を導入していないでも「スニペット(定型文)」機能を利用できます。
- 動作確認は Windows XP sp3 (32bit) × Mery ベータ版 2.8.2 - 2.8.3 (ポータブル) でしかしていません。
一見して 誰得? なマクロですが、「クリップボード履歴」を「スニペット」機能とまとめてツールバーアイコン化できるという素敵なメリットがあります。
- cf. マクロライブラリには「定型文を挿入」機能に特化したマクロも別途あります。
更新履歴
• 2019/08/01: 初版
• 2019/08/06: ・「ピン止めアイテムを貼り付け」コードの変数ミスを修正 ・クリップボード履歴内の重複アイテムを削除する設定を追加。 ・ピン止めアイテムが少ないときはメインメニューにアイテムを表示。 ・snippets.txt 内の空行を無視。 ・クリップボード履歴に長大なデータがあるときの動作速度を少しだけ改善。
• 2019/11/01: ・ステータス表示を追加 ・クリップボードにテキストデータがないときのメニュー構成を変更 ・アイテムの行数(改行数+1)をメニュー内に追加表示 ・メニュー内に表示する改行コードの矢印を「↲」(U+21B2) に変更 ・スニペット機能での「ピン止め/貼り付け」のさいの、改行コードやタブコードではない文字列「\n」や「\t」の扱いを再修正 ・snippets.txt がないときのエラーを修正
ソースコード
- ダウンロード: 「ファイル:クリップボード履歴.zip」(アイコン入り)
#title = "クリップボード履歴..."
#tooltip = "クリップボード履歴 と スニペット"
#icon = "clipboard_history[1].ico"
#include "include/IO.js"
/**
* --------------------------------------------------
* 「クリップボード履歴 と スニペット」マクロ
* sukemaru, 2019/08/01 - 2019/11/01
* --------------------------------------------------
* 「クリップボード履歴」メニューと「スニペット」プラグインと同等の機能を
* ひとつのポップアップメニューに統合します。
*
* 【このマクロの仕様・制限事項】
* ・ver 2.8.1 以前の Mery では使用できません。
* ・「クリップボード履歴」は Mery 本体のメモリストアのものを使用します。
* ※「クリップボードのすべての履歴を削除」したあとでも、
* Windows OS のクリップボードの最新の履歴にテキストデータが残っているときは
* クリップボード履歴メニューに1件だけ表示します。
*
* ・「スニペット」プラグインの設定ファイル snippets.txt の読み書きをします。
* ※「include ライブラリ」が必要です(snippets.txt ファイルの読み書きに使用)。
* ※「スニペット」プラグインを導入していない場合でも、
* 「ピン止めアイテム/スニペット」機能を使用できます。
* snippets.txt ファイルの保存場所は Mery\Plugins\Snippets フォルダです
* (インストーラ版では %AppData%\Mery\Plugins\Snippets フォルダ)。
*
* ・「GetKeyState.exe(キー状態取得実行ファイル) 」で機能を拡張できます。
* → クリップボード履歴のアイテムを Ctrl+クリックした場合、
* アイテムを貼り付けし、貼り終えたアイテムを削除します。
* ※「GetKeyState.exe」は Mery\Macros フォルダに配置してください
* (Macros フォルダ以外の場所にあるなら設定項目でパスを指定する)。
*/
// ---------- ▼ 設定項目 ▼ ---------- //
// ■ クリップボード履歴内の重複アイテムを削除する
var delDupEnable = true; // true: 削除する / false: 削除しない
// ■ ピン止めアイテムをサブメニュー化するアイテム数の閾値
var snSubMenu = 5; // これよりアイテム数が多いときはサブメニューに表示する
// ■ ポップアップメニューを表示する位置
var menuPosMouse = true; // true: マウス位置 / false: キャレット位置
// ■ ポップアップメニューに表示する文字数
var menuWidth = 60;
// ■ GetKeyState.exe のフルパスを指定する場合(「\」記号はふたつがさね「\\」で)
// ※ 未指定 "" なら、 editor.FullName.replace( /[^\\]+$/, "Macros\\GetKeyState.exe" );
var getKeyStatePath = "";
// ---------- ▲ 設定項目 ▲ ---------- //
var start = new Date();
var Fso = new ActiveXObject( "Scripting.FileSystemObject" );
var WshShell = new ActiveXObject( "WScript.Shell" );
// ClipboardData.Get/ClearData() に引数を指定できるのは ver 2.8.1 以降
if ( VersionCheck( "2.8.1" ) ) {
var meryPath = editor.FullName;
var meryDir = meryPath.replace( /[^\\]+$/, "" );
var isPortable = Fso.FileExists( meryPath.replace( /\.exe$/i, ".ini" ) );
var profileDir = ( isPortable )
? meryDir
: WshShell.SpecialFolders( "APPDATA" ) + "\\Mery\\";
var snPath = profileDir + "Plugins\\Snippets\\Snippets.txt";
var snIsExist = Fso.FileExists( snPath );
var gksPath = getKeyStatePath || meryDir + "Macros\\GetKeyState.exe";
var gksIsExist = Fso.FileExists( gksPath );
var $ctrl = 0;
var d = editor.ActiveDocument;
var s = d.selection;
var st = s.Text;
var $status = Status;
// ピン止めアイテム (スニペット)
var snippets = "", snArray = [], snCount = 0 , sn;
if ( snIsExist ) {
snippets = IO.LoadFromFile( snPath, "utf-8" ) || "";
snArray = snippets.replace( /^\n/gm, "" ).replace( /\n$/, "" )
.split( "\n" );
snCount = snArray.length;
sn = ( snCount > snSubMenu );
}
// クリップボード履歴
var cb = ClipboardData;
var cbData = cb.GetData();
var cbArray = [];
var cbData0 = cb.GetData( 0 );
if ( cbData0 ) {
for ( var i = 0, cbItem; ; 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 -- );
}
}
}
// 履歴アイテムを配列に収納する
cbArray.push( cbItem );
}
}
var cbCount = cbArray.length;
var width = String( Math.max( snCount, cbCount || 0 ) ).length;
// ポップアップメニューの準備
var menu = CreatePopupMenu();
var menuFlags = d.ReadOnly ? meMenuGrayed : 0;
var t = ( snSubMenu && snArray[0] ) ? "▼ " : "";
menu.AddPopup( t + "ピン止めアイテム/スニペット (&S) " + t, sm0 = CreatePopupMenu() );
// スニペットのアイテムをメニューに
if ( snCount ) {
for ( var i = 0, id, label, lines; i < snCount; i ++ ) {
id = i + 1;
lines = snArray[i].replace( /[^\t]*\t/, "" ).split( "\\n" ).length;
label = MenuKey( snArray[i].replace( /\t[\s\S]*/, "" ), id, width, true )
+ "\t" + lines + " 行";
if ( label ) {
if ( i < snSubMenu ) {
menu.Add( label, id + 400, menuFlags );
if ( sn ) {
sm0.Add( label, id + 400, menuFlags );
}
}
else {
sm0.Add( label, id + 400, menuFlags );
}
}
}
if ( sn ) {
sm0.Add( "", 0, meMenuSeparator );
}
}
if ( st ) {
sm0.Add( "選択範囲を登録 (&P)", 300 );
}
if ( snIsExist ) {
sm0.Add( "スニペットを編集 (&E) ...", 400 );
}
if ( sn || st || snIsExist ) {
sm0.Add( "", 0, meMenuSeparator );
sm0.Add( "キャンセル & ", 0 );
}
menu.Add( "", 0, meMenuSeparator );
// クリップボード履歴のアイテムをメニューに
if ( cbCount ) {
menu.AddPopup( "▼ クリップボード履歴の一覧 (&C) ▼", sm1 = CreatePopupMenu() );
var sm2 = CreatePopupMenu();
var sm3 = CreatePopupMenu();
for ( var i = 0, id, label, lines; i < cbCount; i ++ ) {
id = i + 1;
lines = cbArray[i].split( "\n" ).length;
label = MenuKey( cbArray[i], id, width )
+ "\t" + lines + " 行";
menu.Add( label, id, menuFlags );
sm2. Add( label, id + 100, menuFlags );
sm3. Add( label, id + 200 );
}
menu.Add( "", 0, meMenuSeparator );
menu.Add( "クリップボードのすべての履歴を削除する (&E)", 500 );
sm1.AddPopup( "貼り付けしてからアイテムを削除 (&M)", sm2 );
sm1.AddPopup( "履歴からアイテムを削除 (&D)", sm3 );
sm1.Add( "", 0, meMenuSeparator );
sm1.Add( "すべての履歴を削除 (&E)", 500 );
}
else if ( cbData.length ) {
menu.Add( "▼ クリップボード ▼", 0, meMenuGrayed );
lines = cbData.split( "\n" ).length;
label = MenuKey( cbData, 1, width )
+ "\t" + lines + " 行";
menu.Add( label, 17, menuFlags );
}
else {
menu.Add( "※ クリップボードにテキストデータはありません ※"
, 0, meMenuGrayed );
}
menu.Add( "", 0, meMenuSeparator );
menu.Add( "キャンセル & ", 0 );
// ステータスバーの表示
if ( menuFlags ) {
Status = " ドキュメントは書き換え禁止です。";
}
else if ( gksIsExist && cbCount ) {
Status = "履歴アイテムの Ctrl+クリック で 「貼り付けしてからアイテムを削除」";
}
else {
Status = "「クリップボード履歴 と スニペット」マクロ";
}
Status += " [ "
+ ( ( new Date() - start ) / 1000 ).toFixed( 3 ).replace( /\./, ". " )
+ " 秒 ]";
// ポップアップメニューを表示
var r = menu.Track( + menuPosMouse );
var confirmStr = "クリップボードのすべての履歴を削除しますか? ";
if ( r == 0 ) {
Status = $status;
}
// 1 ~ 17: 貼り付け
else if ( r <= 16 ) {
d.Write( cbArray[ r -1 ] );
Status = "";
// Ctrl キーを押しながらのときは、貼り付けたアイテムを履歴から削除する
if ( gksIsExist ) {
$ctrl = WshShell.Run( "\"" + gksPath + "\" c", 0, true );
if ( $ctrl == 1 ) {
cb.ClearData( r -1 );
Status = " 履歴からアイテムを削除しました。";
}
}
}
else if ( r == 17 ) {
d.Write( cbData );
Status = "";
}
// 101 ~ 116: 貼り付けして、そのアイテムをクリップボード履歴から削除する
else if ( r > 100 && r < 200 ) {
d.Write( cbArray[ r -101 ] );
cb.ClearData( r -101 );
Status = " 履歴からアイテムを削除しました。";
}
// 201 ~ 216: アイテムをクリップボード履歴から削除する
else if ( r > 200 && r < 300 ) {
cb.ClearData( r -201 );
Status = " 履歴からアイテムを削除しました。";
}
// 401 ~ 499: ピン止めアイテムを貼り付け
else if ( r > 400 && r < 500 ) {
var str = snArray[ r -401 ].replace( /^[^\t]*\t/, "" )
.replace( /(^|[^\\])\\t/g, "$1\t" )
.replace( /(^|[^\\])\\r/g, "$1\r" )
.replace( /(^|[^\\])\\n/g, "$1\n" )
.replace( /\\\\/g, "\\" );
d.Write( str );
Status = "";
}
// 300: 選択範囲を登録(ピン止め)
else if ( r == 300 ) {
var snippetsDir = Fso.GetParentFolderName( snPath );
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( /\\/g, "\\\\" )
.replace( /\t/g, "\\t" )
.replace( /\r?\n/g, "\\n" );
IO.SaveToFile( snPath, str, "utf-8", true );
Status = " 選択範囲をスニペットに登録しました。";
}
// 400: ピン止めアイテムを編集(snippets.txt を開く)
else if ( r == 400 ) {
WshShell.Run( "\"" + editor.FullName + "\" \"" + snPath + "\"" );
Status = " " + snPath;
}
// 500: クリップボード履歴をすべて削除する
else if ( r == 500 && Confirm( confirmStr ) ) {
for ( var i = 0; i < cbCount; i ++ ) {
cb.ClearData( i );
Status = " 履歴からすべてのアイテムを削除しました。";
}
}
}
// ---------- ▼ 関数 ▼ ---------- //
/**
* 関数 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, convLF )
* ポップアップメニューに表示するラベルを生成する
* ・行頭空白を除去、空白文字を圧縮: →「›」(U+203A)
* ・改行記号を可視化: →「↲」(U+21B2) または「⏎」(U+23CE)
* ・削られてしまう「&」を補完
* ・「¥」(U+005C) を「∖」に置換: →「∖」(U+2216)
* ・ゼロ幅や特殊な空白文字を豆腐に置換: →「⊠」(U+22A0) または「▯」(U+25AF)
* ・判別しづらいメタ文字を全角に置換: !"%'(),.:;@[]`{|}
* ・「a-z」を全角に置換
* ・行番号を空白でケタ埋め: EN SPACE「 」(U+2002)
* ・文字数を切り詰め
*/
function MenuKey( str, num, width, convLF ) {
// 2019/08/05 : slice メソッドを追加
if ( convLF ) {
str = str.replace( /(?:\\r)?\\n/g, " ↲ " ); // ⏎
}
var reg = /[\u00A0\u1680\u180e\u2000-\u200A\u2028\u2029\u202F\u205F\uFEFF]/g;
var menuKey = str.replace( /(?:\t|[ ]{3,}|[ ]{2,})+/g, " › " )
.slice( 0, menuWidth + 1 )
.replace( /\r?\n/g, " ↲ " ) // ⏎
.replace( /[&]/g, "&&" )
.replace( /[\\]/g, "∖" )
// .replace( /([^∖!"',.:;`|\t ])([∖]+)/g, "$1 $2" )
.replace( reg, "▯" )
.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;
}
スポンサーリンク