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

提供: MeryWiki
ナビゲーションに移動 検索に移動
Sukemaru (トーク | 投稿記録)
追加コードと追加マクロ
MSY-07 (トーク | 投稿記録)
空行と改行の除去
 
(2人の利用者による、間の7版が非表示)
1行目: 1行目:
== 概要 ==
== 概要 ==
<br>
「クリップボード履歴」と「スニペット (テンプレート)」機能とをひとまとめにしたマクロです。
公式フォーラム[https://www.haijin-boys.com/discussions/4686] にて「[[ヘルプ:ツール#クリップボード履歴|クリップボード履歴]]」メニューの将来的に廃止されることが 予告?検討? されていますが、後継機能としては [[クリップボード履歴|「クリップボード履歴」プラグイン]] の登場により今後も安泰のようです。
* 2in1+α の機能をひとつのツールバーアイコンから利用できます。
* 「クリップボード履歴」と「スニペット」間のデータの受け渡しができるようになります。
* 「クリップボード履歴」機能は Mery 2.8.1 以降で利用できます。
* 「スニペット」機能は [[プラグイン:スニペットプラグイン|スニペットプラグイン]] の設定ファイル '''snippets.txt''' を読み書きするというかたちにしています。


ということで、試験運用中だった自家用マクロを再度リサイクルして「'''クリップボード履歴'''と'''スニペット'''」の統合版ポップアップメニューにしてみました。
<br>
一見して '''誰得?''' なマクロですが、「クリップボード履歴」を「スニペット」機能とまとめてツールバーアイコン化できるというメリットがあります。 また、「クリップボード履歴」と「スニペット」間のデータの受け渡しができるようになります。
<br><br>
:「クリップボード履歴」機能は Mery 2.8.1 以降の『延命措置』の新パラメータ[https://www.haijin-boys.com/discussions/4686#discussion-4729] を利用したものです。
:「スニペット」機能は [[プラグイン:スニペットプラグイン|スニペットプラグイン]] の設定ファイル '''snippets.txt''' を読み書きするというかたちにしています。
<br>
<div class="warningbox">
<div class="warningbox">
*'''「クリップボード履歴」機能は <span style="color:#c00;"> Mery 2.8.1 以降 </span> でしか利用できません。'''
*'''「クリップボード履歴」機能は <span style="color:#c00;"> Mery 2.8.1 以降 </span> でしか利用できません。'''
* あらかじめ「[[includeライブラリ]]」の導入が必要です。
* あらかじめ「[[includeライブラリ]]」の導入が必要です。
</div>
</div>
* 外部実行ファイル「[[GetKeyState.exe(キー状態取得実行ファイル)|GetKeyState.exe]]」で機能を拡張できます(なくても差し支えありません)。
* 外部実行ファイル「[[GetKeyState.exe(キー状態取得実行ファイル)|GetKeyState.exe]]」で機能を拡張できます(なくても差し支えありません)。
* 「[[プラグイン:スニペットプラグイン|スニペットプラグイン]]」を導入していないでも「スニペット(定型文)」機能を利用できます。
* 「[[プラグイン:スニペットプラグイン|スニペットプラグイン]]」を導入していないでも「スニペット(定型文)」機能を利用できます。
: ※ '''snippets.txt''' 内のタブインデントによる階層構造をある程度ポップアップメニューに反映させました。(2019/11/05)
: ※ '''snippets.txt''' 内のタブインデントによる階層構造をある程度ポップアップメニューに反映させられるようになっています。
: ''ref.'' 「スニペットプラグイン」のページの [[プラグイン:スニペットプラグイン#Snippets.txt の書き方|Snippets.txt の書き方]] を参照のこと。
: ''ref.'' 「スニペットプラグイン」のページの [[プラグイン:スニペットプラグイン#Snippets.txt の書き方|Snippets.txt の書き方]] を参照のこと。
: ただし、このマクロでは '''snippets.txt''' 内の空行などの扱いについて [[#「スニペットプラグイン」と異なる部分|「スニペットプラグイン」と異なる部分]] があります。
 
* 動作確認は Windows XP sp3 (32bit) × Mery ベータ版 2.8.6 (ポータブル) 以降でしかしていません。
: ただし、このマクロでは '''snippets.txt''' 内の空行などの扱いについて [[#「スニペットプラグイン」と異なる部分|「スニペットプラグイン」と異なる部分]] があります。
* 動作確認は Windows XP sp3 (32bit) × Mery ベータ版 2.8.6 以降(ポータブル版)でしかしていません。
: なにかしらの支障を来たす不具合が見つかった場合は、このマクロを削除して使用を中止するか、または [https://www.haijin-boys.com/discussions フォーラム] にてご報告ください。
: なにかしらの支障を来たす不具合が見つかった場合は、このマクロを削除して使用を中止するか、または [https://www.haijin-boys.com/discussions フォーラム] にてご報告ください。
<br>
: ''cf.'' マクロライブラリには「[[定型文を挿入]]」機能に特化したマクロも別途あります。
: ''cf.'' マクロライブラリには「[[定型文を挿入]]」機能に特化したマクロも別途あります。
<br>
----
----


<div id="diff1"></div>
<div id="diff1"></div>
=== ツールメニューの「クリップボード履歴」と異なる部分 ===
=== ツールメニューの「クリップボード履歴」と異なる部分 ===
(2019/11/29)
(2019-11-29)
<br>
 
* オプション設定により、<span style="color:#0000c0;">貼り付けたアイテムを「履歴の先頭に移動する」ことができます。</span>
* オプション設定(<code>toTop</code>)により、<span style="color:#0000c0;">貼り付けたアイテムを「履歴の先頭に移動する」ことができます。</span>
: 以後、Ctrl+V でつづけて貼り付けできるようになります。
: 以後、Ctrl+V でつづけて貼り付けできるようになります。


* <span style="color:#0000c0;">クリップボード内に重複するアイテム(完全におなじ文字列データ)がある場合、ひとつを残して古いデータを削除します。</span>
* <span style="color:#0000c0;">クリップボード内に重複するアイテム(完全におなじ文字列データ)がある場合、ひとつを残して古いデータを削除します。</span>
: 16 件分、まるまる活用できます。
: ⇒ 履歴 16 件分、無駄なく活用できます。
: ※重複データを削除するのはこのマクロを実行したときだけです。 通常の「コピー」操作を監視するものではありません。
: ※ 重複データを削除するのはこのマクロを実行したときだけです。 通常の「コピー」操作を監視するものではありません。


* 「'''クリップボードのすべての履歴を削除'''」 「'''履歴からアイテムをひとつ削除'''」 「'''貼り付けしてからアイテムを削除'''」 「'''履歴からアイテムをスニペットに登録'''」 の機能を追加してあります。
* 「'''クリップボードのすべての履歴を削除'''」 「'''履歴からアイテムをひとつ削除'''」 「'''貼り付けしてからアイテムを削除'''」 「'''履歴からアイテムをスニペットに登録'''」 の機能を追加してあります。
: <span style="color:#c00;">ポップアップメニュー内のアイテムの '''Ctrl+クリック''' で「貼り付けしてからアイテムを削除」する機能は、「[[GetKeyState.exe(キー状態取得実行ファイル)|GetKeyState.exe]] 」を導入している場合にかぎり利用できます。</span>
: <span style="color:#c00;">ポップアップメニュー内のアイテムの '''Ctrl+クリック''' で「貼り付けしてからアイテムを削除」する機能は、「[[GetKeyState.exe(キー状態取得実行ファイル)|GetKeyState.exe]] 」を導入している場合にかぎり利用できます。</span>


* 「クリップボードのすべての履歴を削除」 したあとでも、Windows OS のクリップボードの最新の1件がテキストデータである場合は、「クリップボード履歴」にアイテムが表示されます。
(2020-06-27 追加)
: Mery 本来の「クリップボード履歴」機能でも「履歴を消去」したあとにも Ctrl+V によるペーストができることがあるので、これを可視化してあります。
 
<br>
* クリップボード履歴内の複数のアイテムをまとめて貼り付け('''[[#n 番目から m 番目まで貼り付け|つなげて貼り付け]]''')できます。
 
* 「'''追加コピー/追加切り取り'''」コマンドを利用できます([[キーアサイン集#追加切り取り|キーアサイン集バージョン]]とは異なり「追加コピー/追加切り取り」前後のアイテムがクリップボード履歴に累積しません)。
: ''ref.'' 「[[追加コピー・追加切り取り]]」マクロ
 
* <span style="color:#c00;"><u>「[[GetKeyState.exe(キー状態取得実行ファイル)|GetKeyState.exe]] 」を導入していれば</u>、アイテムを '''Ctrl+クリック''' したときに、アイテムを貼り付けし、そのアイテムをクリップボード履歴から削除します。</span>
: ※ 設定項目 <syntaxhighlight lang="javascript" inline>var ctrlEscape = true</syntaxhighlight>)にすると、<code>¥</code> 記号を含むアイテム(ファイルパスなど)を '''Ctrl+クリック''' したときに <code>¥</code> を二重(<code>¥¥</code>)にして貼り付けます。
----
----


<div id="diff1"></div>
<div id="diff1"></div>
=== 「スニペットプラグイン」と異なる部分 ===
=== 「スニペットプラグイン」と異なる部分 ===
(2019/11/29)
(2019-11-29)
<br>
* ピン止めアイテムを '''Ctrl+クリック''' した場合、アイテムを貼り付けし、そのアイテムをクリップボードにコピーします(<span style="color:#c00;">「[[GetKeyState.exe(キー状態取得実行ファイル)|GetKeyState.exe]] 」を導入している場合のみ</span>)。
* ピン止めアイテムを '''Ctrl+クリック''' した場合、アイテムを貼り付けし、そのアイテムをクリップボードにコピーします(<span style="color:#c00;">「[[GetKeyState.exe(キー状態取得実行ファイル)|GetKeyState.exe]] 」を導入している場合のみ</span>)。
: 以後、Ctrl+V でつづけて貼り付けできるようになります。
: 以後、Ctrl+V でつづけて貼り付けできるようになります。


* <span style="color:#0000c0;">このマクロからスニペットに登録したアイテムは、snippets.txt の末尾に追加されます。</span>
* <span style="color:#0000c0;">このマクロからスニペットに登録したアイテムは、snippets.txt の末尾に追加されます。</span>


* snippets.txt 内の「 '''&''' 」記号によるアクセラレータをすべて無視します。
* snippets.txt 内の '''<code>&</code>''' 記号によるアクセラレータをすべて無視します。
: 有効文字列のある行を上から順に連番化し、番号をアクセラレータにします。
: 有効文字列のある行を上から順に連番化し、番号をアクセラレータにします。


* snippets.txt 内の "半角ハイフン - ひとつ + 空白だけの行" をセパレータにしません。
* snippets.txt 内の "半角ハイフン <code>-</code> ひとつ + 空白だけの行" をセパレータにしません。
: (メイン階層を「クリップボード履歴」と共用しているため、体裁上の都合でメインメニュー上でのセパレータ付加は見送り)
: (ポップアップメニューのメイン階層を「クリップボード履歴」と共用しているため、体裁上の都合でメインメニュー上でのセパレータ付加は見送り)
: ※「-」×2 以上であれば、有効文字列として扱います (プラグインでは、これもセパレータになる)。
: ※ <code>-</code>×2 以上であれば、有効文字列として扱います (プラグインでは、これもセパレータになる)。
: ※「ピン止めアイテム/スニペット」サブメニュー内では、階層ごとに区切って全アイテムを列挙します。
: ※「ピン止めアイテム/スニペット」サブメニュー内では、階層ごとに区切って全アイテムを列挙します。


* snippets.txt 内の「空行・空白行」の扱い方などでスニペットプラグインと異なる解釈をしている部分があります。 <span style="color:#0000c0;">このマクロでは、snippets.txt 内の「空行」と「タブ文字/半角空白だけの空白行」を完全に無視するので、「空文字のサブメニュー見出し」を作りません</span> (ただし、全角空白や2つ以上のハイフン -- は有効な文字列として扱います)。
* snippets.txt 内の「空行・空白行」の扱い方などでスニペットプラグインと異なる解釈をしている部分があります。<br><span style="color:#0000c0;">このマクロでは、snippets.txt 内の「空行」と「タブ文字/半角空白だけの空白行」を完全に無視するので、「空文字のサブメニュー見出し」を作りません</span> (ただし、全角空白や2つ以上のハイフン <code>--</code> は有効な文字列として扱います)。
: ポップアップメニューでの表示状態がスニペットプラグインと同じ階層構造にならないことがあります。
: ポップアップメニューでの表示状態がスニペットプラグインと同じ階層構造にならないことがあります。


* <span style="color:#0000c0;">snippets.txt 内で、行頭が "-」×1 + タブ文字" ではじまる行を無視します。</span>
* <span style="color:#0000c0;">snippets.txt 内で、行頭が "<code>-</code>×1 + タブ文字" ではじまる行を無視します。</span>
: 行頭が "-」+タブ文字" の行は "コメントアウトされた行" と見做します。
: 行頭が "<code>-</code>+タブ文字" の行は "コメントアウトされた行" と見做します。
: ※ スニペットプラグインではタブ文字のあとに文字列があれば -をラベルとしてメニューのアイテムに追加しますが、このマクロでは無効な行となります。
: ※ スニペットプラグインではタブ文字のあとに文字列があれば <code>-</code> をラベルとしてメニューのアイテムに追加しますが、このマクロでは無効な行となります。


* <span style="color:#c00;">snippets.txt 内の空行やタブインデントでのクループ化が適切でない部分は、ポップアップメニューに正しく反映されません。</span> その行または段落/グループをスキップし、ポップアップメニューに表示しません。
* <span style="color:#c00;">snippets.txt 内の空行やタブインデントでのクループ化が適切でない部分は、ポップアップメニューに正しく反映されません</span> (その行または段落/グループをスキップし、ポップアップメニューに表示しません)。
: <span style="color:#0000c0;">※ タブインデントの階層を深くするさいは、かならず一段ずつ下げてください。</span> 二段以上の差があるとエラーの元になったり、または予期せぬ階層に表示されたりします (これはスニペットプラグインでも起きえるものであり、不具合ではありません)。
: <span style="color:#0000c0;">※ タブインデントの階層を深くするさいは、かならず一段ずつ下げてください。</span><br> 二段以上の差があるとエラーの元になったり、または予期せぬ階層に表示されたりします (これはスニペットプラグインでも起きえるものであり、不具合ではありません)。
: ※ 階層を浅くするさいは、二段以上の差があっても構いません。
: ※ 階層を浅くするさいは、二段以上の差があっても構いません。


(2020-06-27 追加)
* オプション設定により snippets.txt 内で <code>@@@</code> または <code>@@@@</code> を含むアイテムの場合、「[[#ピン止めアイテムで選択範囲を囲う|選択範囲を囲う]]」ことができます。
=== n 番目から m 番目まで貼り付け ===
クリップボード履歴内の連続する複数のアイテムを '''まとめて貼り付け''' します。
* 「n 番目から m 番目まで貼り付け」
: 指定した複数のアイテムを '''そのままつなげて''' 貼り付けます。
* 「n 番目から m 番目まで 任意の文字列 でつないで貼り付け...」
: 指定した複数のアイテムを '''入力ダイアログで指定した文字列''' でつなげて貼り付けます。
* 「n 番目から m 番目までを 改行 でつないで貼り付け」
: 指定した複数のアイテムを '''改行でつなげて''' 貼り付けます。
※ 「n 番目から m 番目までを 半角空白 でつないで貼り付け」「n 番目から m 番目までを 空行 でつないで貼り付け」コマンドもあります(ソースコード内 340 行目付近でコメントアウト)。
追加コマンドを選択すると入力ダイアログが2回(「任意の文字列」では3回)表示されますので、ポップアップメニュー内での '''履歴アイテムの番号''' を指定してください。
※ コピーした順番どおりにつなげて貼り付けするばあいは、「さいしょのアイテムの番号」'''>'''「さいごのアイテムの番号」です。
 '''''e.g.''''' 最新の3件をコピーした順につなげて貼り付けするなら
:「さいしょのアイテムの番号」= '''3'''
:「さいごのアイテムの番号」= '''1'''
※ 入力ダイアログが "空の状態" または "無効な文字列のみが入力された状態" でキャンセルされたばあいは、なにも貼り付けしません。
※ 設定項目 <syntaxhighlight lang="javascript" inline>var unitToTop = true;</syntaxhighlight> にすると、つなげたテキストデータをクリップボード履歴の先頭に追加登録します (ただし 16 件目にあったアイテムは消えます)。<br>
<syntaxhighlight lang="javascript" inline>var unitToTop = true;</syntaxhighlight> でも <syntaxhighlight lang="javascript" inline>var unitToTop = false;</syntaxhighlight> でも、つなげる前の各アイテムは削除しません (削除する仕様だと番号指定をまちがえたときに手戻りできなくなってしまう)。
=== ピン止めアイテムで選択範囲を囲う ===
設定項目 <syntaxhighlight lang="javascript" inline>var sandwich = true</syntaxhighlight> にすると、snippets.txt 内で <code>@@@</code> または <code>@@@@</code> を含むアイテムは「選択範囲の囲い込み」に利用できるようになります。
※ <span style="color:#c00;">囲い込み用文字列が改行を含んでいる場合、マルチカーソル/複数選択に非対応です</span>(正しく囲い込みできません)。
* <code>@</code> を '''3つ'''(<code>@@@</code>)ふくむアイテムは、'''選択範囲全体''' を <code>@@@</code> の前後の文字列で囲います。
:; ''e.g.''
:・snippets.txt のアイテム:<code><nowiki><pre>\n@@@\n</pre></nowiki></code>
:・選択範囲の文字列:
var d = editor.ActiveDocument;
var s = d.selection;
:: ▼ 結果 ▼
<syntaxhighlight lang="javascript">
<pre>
var d = editor.ActiveDocument;
var s = d.selection;
</pre>
</syntaxhighlight>
* <code>@</code> を '''4つ'''(<code>@@@@</code>)ふくむアイテムは、'''選択範囲の各行''' を <code>@@@@</code> の前後の文字列で囲います。
:; ''e.g.''
:・snippets.txt のアイテム:<code><nowiki><code>@@@@</code></nowiki></code>
:・選択範囲の文字列:
var d = editor.ActiveDocument;
var s = d.selection;
:: ▼ 結果 ▼
<syntaxhighlight lang="javascript">
<code>var d = editor.ActiveDocument;</code>
<code>var s = d.selection;</code>
</syntaxhighlight>
* 設定項目 <syntaxhighlight lang="javascript" inline>var cheese = "@"</syntaxhighlight> の <code>@</code> を任意の文字に変更可。


== ダウンロード ==
== ダウンロード ==
; >> 「[[ファイル:クリップボード履歴.zip ‎]]」(アイコン入り)
; >> 「[[ファイル:クリップボード履歴.zip ‎]]」(アイコン入り)
: 最終更新: 2019/11/29
: 最終更新: 2020-06-27
: '''sunipetts.txt''' は「スニペットプラグイン」のものがあれば共用、なければ新規に生成(要 [[includeライブラリ]])しますので、マクロ本体の JS ファイルとアイコンだけをご利用ください。
: '''sunipetts.txt''' は「スニペットプラグイン」のものがあれば共用、なければ新規に生成(要 [[includeライブラリ]])しますので、マクロ本体の JS ファイルとアイコンだけをご利用ください。
 
* ポップアップメニュー内の連番アイテムの桁埋め(右寄せ/空白埋め)用の [https://ja.wikipedia.org/wiki/スペース#コンピュータにおけるスペース 空白文字] を設定する項目 <syntaxhighlight lang="javascript" inline>var b1 = " ";</syntaxhighlight> を追加し、初期値を半角空白(U+0020 <code>" "</code>)にしました(※ これまで MS UI Gothic に最適化してあった桁埋め方法をカスタマイズできるようにした)。
: ・MS UI Gothic では2分アキ(EN SPACE) <code>"\u2002"</code><br> ・Meiryo UI では和字間隔(全角空白) <code>"\u3000"</code><br> ・Segoe UI では図形間隔(FIGURE SPACE) <code>"\u2007"</code> にすると具合がよいようです。


== ソースコード ==
== ソースコード ==
<source lang="javascript" style="height:60em; overflow:auto;" highlight="19-35,240-242,275-277">
<syntaxhighlight lang="javascript" style="height:120em; overflow:auto;" highlight="29-38,41-47,50-57,60-67,70-78,81-91,340,342">
#title = "クリップボード履歴..."
#title = "クリップボード履歴..."
#tooltip = "クリップボード履歴 と スニペット"
#tooltip = "クリップボード履歴 と スニペット"
#include "include/IO.js"
#icon = "clipboard_history[1].ico"
#icon = "clipboard_history[1].ico"
#include "include/IO.js"
// #icon = "Mery用 マテリアルデザインっぽいアイコン.icl",314


/**
/**
  * --------------------------------------------------
  * --------------------------------------------------
  * 「クリップボード履歴 と スニペット」マクロ
  * 「クリップボード履歴 と スニペット」マクロ
  *  sukemaru, 2019/08/01 - 2019/11/29
  *  sukemaru, 2019-08-01 - 2020-06-27
*  https://www.haijin-boys.com/wiki/「クリップボード履歴」メニューのマクロ化
  * --------------------------------------------------
  * --------------------------------------------------
  * 「クリップボード履歴」メニューと「スニペット」プラグインと同等の機能を
  * 「クリップボード履歴」メニューと「スニペット」プラグインと同等の機能を
  *  ひとつのポップアップメニューに統合します。
  *  ひとつのポップアップメニューに統合します。
  *  
  *  
  * ※ まだまだ不具合が残っているかもしれません。
  * ※ Ver 2.8.0 以前の Mery では「クリップボード履歴」機能を使用できません。
  * ※ ソースコードの末尾に【能書き】
  * ※「スニペット」プラグインを導入していない場合でも
*  「スニペット」機能(ピン止め)を使用できます。
*
* ※「include ライブラリ」が必要です。
*    https://www.haijin-boys.com/wiki/includeライブラリ
* ※「GetKeyState.exe」で機能を拡張できます。
*    https://www.haijin-boys.com/wiki/GetKeyState.exe(キー状態取得実行ファイル)
  */
  */


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


// ■ 貼り付けたアイテムをクリップボード履歴の先頭に移動する
// ■ 貼り付けたアイテムをクリップボード履歴の先頭に移動する
var toTop = false; // true: 移動する / false: 移動しない
var toTop = false;         // true: 移動する / false: 移動しない
 
  // true にすると、Ctrl+V でつづけて貼り付け可
  // false なら、クリップボードのアイテムの順番を維持する
 
  // ■ toTop = true のとき
  //  「n 番目から m 番目までをつなげて貼り付け」したアイテムを
  //    クリップボード履歴の先頭に登録する(※ただし 16 番目だったアイテムが消える)
  var unitToTop = false;    // true: 登録する / false: 登録しない
 
 
// ■ 「任意の文字列でつなげて貼り付け」で入力した文字列の一時記憶方法
var tagType = 2;
 
  // 0 : 一時記憶なし
  // 1 : タブ(文書)ごとに一時記憶する(Document.Tag)
  // 2 : ウインドウごとに一時記憶する(Editor.Tag)
  // 3 : すべてのタブとウインドウ共通で一時記憶する(window.Tag)
 
 
// ■ "@@@" をふくむピン止めアイテムを "選択範囲の囲いこみ用" にする
var sandwich = false;      // true: する / false: しない
var cheese = "@"            // 初期値:"@"
 
  // sandwich = true なら、ピン止めアイテムが "@@@" をふくむときに...
  // ⇒ "@@@@" (cheese × 4) で区切られた前後の文字列で「選択範囲の各行」を囲う
  // ⇒ "@@@"  (cheese × 3) で区切られた前後の文字列で「選択範囲全体」を囲う
  // cheese = "@" の「@」は任意の文字に変更可
 


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


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


// ■ 半角英文字を全角で表示する [!"%'(),.:;@\[\]`a-z{|}]
// ■ 半角英小文字や ascii 記号を全角で表示する
var toFullWidth = false; // (true する / false しない)
var toWideWidth = 0;        // (0 しない / 1 文字間を広げる / 2 全角にする)
 
 
// ■ 連番アイテムの桁埋め(右寄せ/空白埋め)用の空白文字の定義
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"
    にすると具合がよさそうですが、ウエイトによって幅が変わるので... */
 


// ■ GetKeyState.exe のフルパスを指定する場合( \ 記号はふたつがさね「\\」で)
// ■ GetKeyState.exe のフルパスを指定する場合( \ 記号はふたつがさね「\\」で)
// 未指定 "" なら、Mery インストールフォルダの Macros\GetKeyState.exe
// 未指定 "" なら、Mery インストールフォルダの Macros\GetKeyState.exe
var getKeyStatePath = ""; // ※ GetKeyState.exe なしのときも "" にする
var getKeyStatePath = "";   // ※ GetKeyState.exe なしのときも "" にする
 
  // ■ クリップボードのアイテムの Ctrl+クリックで
  //    ¥ を ¥¥ に置換して貼り付ける
  var ctrlEscape = false;
 
    // true なら、 Ctrl+クリックで ¥ を ¥¥ に置換して貼り付け
    // false なら、Ctrl+クリックで 貼り付けしてからアイテムを削除
    // GetKeyState.exe がない場合は無効


// ---------- ▲ 設定項目 ▲ ---------- //
// ---------- ▲ 設定項目 ▲ ---------- //
122行目: 243行目:
var start = new Date();
var start = new Date();


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


var meryPath = editor.FullName;
var meryPath     = editor.FullName;
var meryDir = meryPath.replace( /[^\\]+$/, "" );
var meryDir     = meryPath.replace( /[^\\]+$/, "" );
var isPortable = Fso.FileExists( meryPath.replace( /\.exe$/i, ".ini" ) );
var isPortable   = Fso.FileExists( meryPath.replace( /\.exe$/i, ".ini" ) );
var profileDir = ( isPortable )
var profileDir   = ( isPortable )
                 ? meryDir
                 ? meryDir
                 : WshShell.SpecialFolders( "APPDATA" ) + "\\Mery\\";
                 : WshShell.SpecialFolders( "APPDATA" ) + "\\Mery\\";
var snPath = profileDir + "Plugins\\Snippets\\Snippets.txt";
var snPath       = profileDir + "Plugins\\Snippets\\Snippets.txt";
var snIsExist = Fso.FileExists( snPath );
var snIsExist   = Fso.FileExists( snPath );
var gksPath = getKeyStatePath || meryDir + "Macros\\GetKeyState.exe";
var gksIsExist = Fso.FileExists( gksPath );
var $ctrl = 0;
var versionCheck = VersionCheck( "2.8.1" );
var versionCheck = VersionCheck( "2.8.1" );
var d = editor.ActiveDocument;
var d  = editor.ActiveDocument;
var s = d.selection;
var s  = d.selection;
var st = s.Text;
var st = s.Text;
var $status = Status;
var $status = Status;
var gksPath  = getKeyStatePath || meryDir + "Macros\\GetKeyState.exe";
var gksExists = Fso.FileExists( gksPath );
var $ctrl = false;
var $Ctrl = function() {
  return ( gksExists
  && WshShell.Run( '"' + gksPath + '" c', 0, true ) === 1 );
}
var p = "";
var tagKey = "ClipboardMenu";
var ham  = cheese ? cheese.replace( /[.*+?^=!:${}()|[\]\/\\]/g, "\\$&" ) : "";
sandwich = ham ? sandwich : false;
var b1 = ( ! blankChr || ( "" + blankChr ).length > 1 )
      ? " " : blankChr;
Object.prototype.PadSpace = function(len, chr) {
  var dgt = String(this);
  len = Number(len) || dgt.length;
  chr = chr ? chr.toString() : b1;
  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);
};


// ピン止めアイテム (スニペット)の準備
// ピン止めアイテム (スニペット)の準備
158行目: 301行目:


// クリップボード履歴の準備
// クリップボード履歴の準備
var cb = ClipboardData;
var cb     = ClipboardData;
var cbData = cb.GetData();
var cbData  = cb.GetData();
var cbArray = [];
var cbArray = [];
if ( versionCheck ) {
if ( versionCheck ) {
   var cbData0 = cb.GetData( 0 );
   var cbData0 = cb.GetData( 0 );
   if ( cbData0 ) {
   if ( cbData0 ) {
     outer:
     outer:
     for ( var i = 0, cbItem; ; i ++ ) {
     for ( var i = 0, cbItem; i < 16 ; i ++ ) {
       cbItem = cb.GetData( i ) || "";
       cbItem = cb.GetData( i ) || "";
       if ( ! cbItem ) { break outer; }
       if ( ! cbItem ) { break outer; }
173行目: 316行目:
         cbNextItem = cb.GetData( j ) || "";
         cbNextItem = cb.GetData( j ) || "";
         if ( ! cbNextItem ) { break inner; }
         if ( ! cbNextItem ) { break inner; }
         if ( cbItem == cbNextItem ) {
         if ( cbItem === cbNextItem ) {
           cb.ClearData( j -- );
           cb.ClearData( j -- );
         }
         }
182行目: 325行目:
   }
   }
}
}
var cbCount = cbArray.length;
var cbCount  = cbArray.length;
var width = String( Math.max( snCount, cbCount ) ).length;
var numWidth = String( Math.max( snCount, cbCount ) ).length;




189行目: 332行目:
var menu = CreatePopupMenu();
var menu = CreatePopupMenu();
var grayFlag = d.ReadOnly ? meMenuGrayed : 0;
var grayFlag = d.ReadOnly ? meMenuGrayed : 0;
var t = ( snCount ) ? "▼ " : "";
var label,  id;


// 「ピン止めアイテム/スニペット」サブメニュー
// 「ピン止めアイテム/スニペット」サブメニュー
var sm0 = CreatePopupMenu();
var subMenu0 = CreatePopupMenu();
var t  = ( snCount ) ? "▼ " : "";
menu.AddPopup( t + "ピン止めアイテム/スニペット (&S) " + t, subMenu0 );
menu.AddPopup( t + "ピン止めアイテム/スニペット (&S) " + t, sm0 );
if ( st ) {
if ( st ) {
   sm0.Add( "選択範囲を登録 (&P)", 300 );
   subMenu0.Add( "選択範囲を登録 (&P)", 300 );
}
}
if ( snIsExist ) {
if ( snIsExist ) {
   sm0.Add( "スニペットを編集 (&E) ...", 400 );
   subMenu0.Add( "スニペットを編集 (&E) ...", 400 );
}
}
if ( st || snIsExist ) {
if ( st || snIsExist ) {
   sm0.Add( "", 0, meMenuSeparator );
   subMenu0.Add( "", 0, meMenuSeparator );
   sm0.Add( "キャンセル & ", 0 );
   subMenu0.Add( "キャンセル & ", 0 );
}
}
if ( snCount ) {
if ( snCount ) {
   sm0.Add( "", 0, meMenuSeparator );
   subMenu0.Add( "", 0, meMenuSeparator );
}
}


// スニペットのアイテムをメニューに
// スニペットのアイテムをメニューに
if ( snText ) {
if ( snText ) {
   var label, label0, label1, label2, tab1, tab2;
   var sn0, sn1, sn2, tab1, tab2;
   var subArray = []; // SubMenu Array
   var subArray = [];           // サブメニュー: 10 階層までを想定
   for ( var i = 0; i < 10; i ++ ) { subArray.push( [] ); }
   for ( var i = 0; i < 10; i ++ ) {
   var subId, subId1, subId2;
    subArray.push( [] );
   for ( var i = 0, j = 1, tab0 = 0, id; i < snCount; i ++, j ++ ) {
  }
   var subID, subID1, subID2;
   for ( var i = 0, j = 1, tab0 = 0; i < snCount; i ++, j ++ ) {
     try {
     try {
       label1 = snArray[i];
       sn1 = snArray[i];
       label2 = snArray[j] || "";
       sn2 = snArray[j] || "";   // 次の有効文字列の行(空行やセパレータ行は除外)
       tab1   = ( label1.charAt( 0 ) == "\t" ) ? label1.search( /[^\t]/ ) : 0;
      // 行頭のタブのケタ数(階層の深さ)
       tab2   = ( label2.charAt( 0 ) == "\t" ) ? label2.search( /[^\t]/ ) : 0;
       tab1 = ( sn1.charAt( 0 ) == "\t" )
       label = MenuKey( label1.replace( /^\t+|\t+[^\t]*$/g, "" )
          ? sn1.search( /[^\t]/ ) : 0;
                      , j, width, menuWidth, true );
       tab2 = ( sn2.charAt( 0 ) == "\t" )
       id     = j + 400;
          ? sn2.search( /[^\t]/ ) : 0;
       // メインメニューにサブメニュー項目を追加
      // メニューに表示させるラベル部分とメニューID
       label = MenuKey( sn1.replace( /^\t+|\t+[^\t]*$/g, "" )
                    , j, numWidth, menuWidth, b1, true );
       id = j + 400;
       // メインメニューにサブメニュー項目(見出し)を追加
       if ( tab1 == 0 && tab1 + 1 == tab2 ) {
       if ( tab1 == 0 && tab1 + 1 == tab2 ) {
         subId = subArray[0].length;
         subID = subArray[0].length;
         subArray[0].push( CreatePopupMenu() );
         subArray[0].push( CreatePopupMenu() );
         if ( grayFlag ) {
         if ( grayFlag ) {
           menu.Add( label + "\t▶", id, grayFlag );
           menu.Add( label + "\t\u25B6", id, grayFlag ); //「▶」(\u25B6)
         }
         }
         else {
         else {
           menu.AddPopup( label, subArray[0][ subId ] );
           menu.AddPopup( label, subArray[0][ subID ] );
         }
         }
         sm0.Add( "", 0, meMenuSeparator );
         subMenu0.Add( "", 0, meMenuSeparator );
         label0 = false;
         sn0 = false;
       }
       }
       // メインメニューにアイテムを追加
       // メインメニュー直下にアイテムを追加
       else if ( tab1 == 0 && ( tab1 == tab2 || tab1 + 1 < tab2 ) ) {
       else if ( tab1 == 0 && ( tab1 == tab2 || tab1 + 1 < tab2 ) ) {
         menu.Add( label, id, grayFlag );
         menu.Add( label, id, grayFlag );
         if ( tab1 < tab0 && label0 ) {
         if ( tab1 < tab0 && sn0 ) {
           sm0.Add( "", 0, meMenuSeparator );
           subMenu0.Add( "", 0, meMenuSeparator );
         }
         }
         sm0 .Add( label, id, grayFlag );
         subMenu0 .Add( label, id, grayFlag );
         label0 = true;
         sn0 = true;
       }
       }
       // サブメニュー内にサブメニュー項目を追加
       // サブメニュー内にサブメニュー項目(見出し)を追加
       else if ( tab1 > 0 && tab1 + 1 == tab2 ) {
       else if ( tab1 > 0 && tab1 + 1 == tab2 ) {
         subId1 = subArray[ tab1 ].length;
         subID1 = subArray[ tab1 ].length;
         subId2 = subArray[ tab1 -1 ].length - 1;
         subID2 = subArray[ tab1 - 1 ].length - 1;
         subArray[ tab1 ].push( CreatePopupMenu() );
         subArray[ tab1 ].push( CreatePopupMenu() );
         subArray[ tab1 -1 ][ subId2 ].AddPopup( label, subArray[ tab1 ][ subId1 ] );
         subArray[ tab1 - 1 ][ subID2 ].AddPopup(
         if ( label0 ) {
          label, subArray[ tab1 ][ subID1 ] );
           sm0.Add( "", 0, meMenuSeparator );
         if ( sn0 ) {
           subMenu0.Add( "", 0, meMenuSeparator );
         }
         }
         label0 = false;
         sn0 = false;
       }
       }
       // サブメニューにアイテムを追加
       // サブメニュー内にアイテムを追加
       else if ( tab1 > 0 && ( tab1 >= tab2 || tab1 + 1 < tab2 ) ) {
       else if ( tab1 > 0 && ( tab1 >= tab2 || tab1 + 1 < tab2 ) ) {
         subId = subArray[ tab1 -1 ].length - 1;
         subID = subArray[ tab1 - 1 ].length - 1;
         subArray[ tab1 -1 ][ subId ].Add( label, id, grayFlag );
         subArray[ tab1 - 1 ][ subID ].Add( label, id, grayFlag );
         if ( tab1 < tab0 && label0 ) {
         if ( tab1 < tab0 && sn0 ) {
           sm0.Add( "", 0, meMenuSeparator );
           subMenu0.Add( "", 0, meMenuSeparator );
         }
         }
         sm0.Add( label, id, grayFlag );
         subMenu0.Add( label, id, grayFlag );
         label0 = true;
         sn0 = true;
       }
       }
       tab0 = tab1;
       tab0 = tab1;
     }
     }
     catch( e ) {
     catch( e ) { ;
       /**
       /**
       * snArray[i] (行_A) と次の有効文字列の行 snArray[j] (行_B) の
       * snArray[i] (行_A) と次の有効文字列の行 snArray[j] (行_B) の
284行目: 435行目:
}
}
menu.Add( "", 0, meMenuSeparator );
menu.Add( "", 0, meMenuSeparator );


// クリップボード履歴のアイテムをメニューに
// クリップボード履歴のアイテムをメニューに
if ( versionCheck ) {
if ( cbCount ) {
   if ( cbCount ) {
   var subMenu1 = CreatePopupMenu(); // クリップボード履歴の一覧
    var sm1  = CreatePopupMenu();
  var subMenu2 = CreatePopupMenu(); // 履歴のアイテムをスニペットに登録
    var sm2  = CreatePopupMenu();
  var subMenu3 = CreatePopupMenu(); // 貼り付けしてからアイテムを削除
    var sm3  = CreatePopupMenu();
  var subMenu4 = CreatePopupMenu(); // 履歴からアイテムを削除
    var sm4  = CreatePopupMenu();
  menu.AddPopup( "▼ クリップボード履歴の一覧 (&H) ▼", subMenu1 );
    menu.AddPopup( "▼ クリップボード履歴の一覧 (&C) ▼", sm1 );
  for ( var i = 0; i < cbCount; i ++ ) {
    for ( var i = 0, id, label; i < cbCount; i ++ ) {
    id = i + 1;
      id = i + 1;
    label = MenuKey( cbArray[i], id, numWidth, menuWidth, b1 );
      label = MenuKey( cbArray[i], id, width, menuWidth );
    menu.Add( label, id, grayFlag );
      menu.Add( label, id, grayFlag );
    subMenu2. Add( label, id + 100, grayFlag );
      sm2. Add( label, id + 100, grayFlag );
    subMenu3. Add( label, id + 200 );
      sm3. Add( label, id + 200 );
    subMenu4. Add( label, id + 300 );
      sm4. Add( label, id + 300 );
    }
    menu.Add( "", 0, meMenuSeparator );
    menu.Add( "クリップボードのすべての履歴を削除する (&E)", 99 );
    sm1.AddPopup( "履歴からアイテムをスニペットに登録 (&P)", sm4 );
    sm1.AddPopup( "貼り付けしてからアイテムを削除 (&M)", sm2 );
    sm1.AddPopup( "履歴からアイテムを削除 (&D)", sm3 );
    sm1.Add( "", 0, meMenuSeparator );
    sm1.Add( "すべての履歴を削除 (&E)", 99 );
    sm1.Add( "", 0, meMenuSeparator );
    sm1.Add( "キャンセル & ", 0 );
   }
   }
  // subMenu1.Add( "", 0, meMenuSeparator );
  subMenu1.AddPopup( "履歴のアイテムをスニペットに登録 (&P)", subMenu4 );
  subMenu1.AddPopup( "貼り付けしてからアイテムを削除 (&M)", subMenu2 );
  subMenu1.AddPopup( "履歴からアイテムを削除 (&D)", subMenu3 );
  subMenu1.Add( "", 0, meMenuSeparator );
  subMenu1.Add( "すべての履歴を削除 (&E)", 99 );
  subMenu1.Add( "", 0, meMenuSeparator );
  subMenu1.Add( "キャンセル & ", 0 );
}
}
if ( ! cbCount && cbData.length ) {
// Windows のクリップボードデータ
else if ( cbData.length ) {
  label = MenuKey( cbData, 1, numWidth, menuWidth, b1 );
   menu.Add( "▼ クリップボード ▼", 0, meMenuGrayed );
   menu.Add( "▼ クリップボード ▼", 0, meMenuGrayed );
  label = MenuKey( cbData, 1, width, menuWidth );
   menu.Add( label, 17, grayFlag );
   menu.Add( label, 17, grayFlag );
}  
}  
else if ( ! cbCount && ! cbData.length ) {
else {
   menu.Add( "※ クリップボードにテキストデータはありません ※"
   menu.Add( "※ クリップボードにテキストデータはありません ※"
           , 0, meMenuGrayed );
           , 0, meMenuGrayed );
}
if ( ! ctrlEscape && ( cbData0 || cbData ).indexOf( "\\" ) > -1 ) {
  menu.Add( "", 0, meMenuSeparator );
  menu.Add( "\u00A5 を \u00A5\u00A5 に置換して "    //「¥」(\u00A5)
          + "履歴の先頭アイテム を貼り付け (&E)"
          , 18, grayFlag );
}
if ( cbCount > 1 ) {
  var u1 = "履歴の n 番目から m 番目までを";
  var u2 = "つなげて貼り付け ";
  menu.Add( "", 0, meMenuSeparator );
  menu.Add( u1 + u2 + "(&M)", 94, grayFlag );
  menu.Add( u1 + " 任意の文字列 で" + u2 + "(&P)...", 95, grayFlag );
  // menu.Add( u1 + " 半角空白 で" + u2 + "(&W)", 96, grayFlag );
  menu.Add( u1 + " 改行 で" + u2 + "(&N)", 97, grayFlag );
  // menu.Add( u1 + " 空行 で" + u2 + "(&B)", 98, grayFlag );
}
  menu.Add( "", 0, meMenuSeparator );
  menu.Add( "追加コピー (&C)", 92 );
  menu.Add( "追加切り取り (&T)", 93, grayFlag );
if ( cbData || cbCount ) {
  menu.Add( "", 0, meMenuSeparator );
  menu.Add( "クリップボードのすべての履歴を削除する (&E)", 99 );
}
}


327行目: 504行目:


// ステータスバーの表示
// ステータスバーの表示
if ( grayFlag ) {
Status = ( grayFlag )
  Status = " ドキュメントは書き換え禁止です。";
      ? " ドキュメントは書き換え禁止です。"
}
      : ( ! versionCheck )
else if ( ! versionCheck ) {
      ? " 「クリップボード履歴」機能の動作要件は"
  Status = " 「クリップボード履歴」機能の動作要件は"
         + " \"Mery Ver 2.8.1\" 以上です。"
         + " \"Mery ver 2.8.1\" 以上です。";
      : ( ctrlEscape && gksExists && ( cbCount || cbData ) )
}
      ? " 履歴アイテムの Ctrl+クリック で"  //「¥」(\u00A5)
else if ( gksIsExist && cbCount ) {
        + " \u00A5 を \u00A5\u00A5 に置換して貼り付けます。"
  Status = " 履歴アイテムの Ctrl+クリック で"
      : ( gksExists && cbCount )
         + " 「貼り付けしてからアイテムを削除」";
      ? " 履歴アイテムの Ctrl+クリック で"
}
         + " 「貼り付けしてからアイテムを削除」"
else if ( gksIsExist && snCount ) {
      : ( gksExists && snCount )
  Status = " ピン止めアイテムの Ctrl+クリック で"
      ? " ピン止めアイテムの Ctrl+クリック で"
         + " 「クリップボードにコピーして貼り付け」";
         + " 「クリップボードにコピーして貼り付け」"
}
      : " 「クリップボード履歴 と スニペット」マクロ";
else {
  Status = " 「クリップボード履歴 と スニペット」マクロ";
}
Status += " [ "
Status += " [ "
       + ( ( new Date() - start ) / 1000 ).toFixed( 3 ).replace( /\./, ". " )
       + ( ( new Date() - start ) / 1000 ).toFixed( 3 ).replace( /\./, ". " )
352行目: 526行目:
// ポップアップメニューを表示
// ポップアップメニューを表示
var r = menu.Track( + menuPosMouse );
var r = menu.Track( + menuPosMouse );
var confirmStr = "クリップボードのすべての履歴を削除しますか? ";
Status = $status;


if ( r == 0 ) {  /* Status = $status */ ; }


// 1 ~ 17: クリップボード履歴のアイテムを貼り付け
main: {
else if ( r <= 16 ) {
  if ( ! r ) {
   s.Text = cbArray[ r -1 ];
    Status = $status;  break main;
   // Ctrl キーを押しながらのときは、貼り付けしてからアイテムを削除
  }
   if ( gksIsExist ) {
  editor.ExecuteCommandByID( MEID_WINDOW_ACTIVE_PANE = 2189 );
     $ctrl = WshShell.Run( "\"" + gksPath + "\" c", 0, true );
 
     if ( $ctrl == 1 ) {
 
       cb.ClearData( r -1 );
  // 94, 95, 96, 97, 98: n 番目から m 番目まで貼り付け
  unit:
  if ( r >= 94 && r <= 98 ) {
    $ctrl = $Ctrl();
    // 半角変換した ascii 文字列を返す関数
    var ToHalfWidth = function( strVal ) {
      return strVal.replace( /[!-~]/g, function( tmp ) {
          return String.fromCharCode( tmp.charCodeAt(0) - 0xFEE0 )
    } ) };
 
    // 入力ダイアログ×2回 ※全角/半角の数字のみを有効文字列として取得する
    var p1 = + ToHalfWidth( Prompt( "さいしょのアイテムの番号", "" ) )
              .replace( /\D/g, "" );
    if ( p1 <= 0 || p1 > cbCount ) {
      Status = " キャンセル";  break unit;
    }
    var p2 = + ToHalfWidth( Prompt( "さいごのアイテムの番号", p1 ) )
              .replace( /\D/g, "" );
    if ( p2 <= 0 || p2 > cbCount ) {
      Status = " キャンセル";  break unit;
    }
 
    // 入力された番号が同一なら、通常の貼り付けコマンド r == 1~16 に飛ぶ
    if ( p1 === p2 ) {
      r = p1;  break unit;
    }
 
    // 上から順 または 下から順
    else {
      var str = "";
      var b = ( r === 95 ) ? AddStrPrompt( tagType, tagKey )
            : ( r === 96 ) ? " "
            : ( r === 97 ) ? "\n"
            : ( r === 98 ) ? "\n\n"
            :/* r ===94 */   "";
      if ( p1 < p2 ) {  // 上から順
        for ( var i = p1; i <= p2; i ++ ) {
          str += cbArray[ i - 1 ] + b;
        }
      }
      else {            // 下から順
        for ( var i = p2; i <= p1; i ++ ) {
          str = cbArray[ i - 1 ] + b + str;
        }
      }
      str = str.slice( 0, b ? - b.length : str.length );
 
      // 貼り付け ※ Ctrl キーを押しながらのときは、 ¥ を ¥¥ に置換
      s.Text = ( ctrlEscape && $ctrl )
            ? str.replace( /\\/g, "\\\\" ) : str;
 
      // まとめたデータを履歴の先頭に登録
      if ( toTop && unitToTop ) { cb.SetData( pStr ); }
    }
  } // unit:{}
 
 
  // 1 ~ 17: クリップボード履歴のアイテムを貼り付け
   // Ctrl キーを押しながらのときは、 ¥ を ¥¥ に置換
   if ( ctrlEscape && r > 0 && r <= 17 ) {
     $ctrl = $ctrl || $Ctrl();
    var str = ( r === 17 ) ? cbData : cbArray[ r -1 ];
    s.Text = $ctrl ? str.replace( /\\/g, "\\\\" ) : str;
 
    // 貼り付けたデータを履歴の先頭に移動
    if ( toTop && r <= 16 ) {
      cbArray.unshift( cbArray.splice( r - 1, 1 ) );
      for ( var i = 0; i < cbCount; i ++ ) {
        cb.SetData( cbArray[i], i );
      }
      cb.SetData( cbArray[0] );
     }
  }
 
  else if ( r > 0 && r <= 16 ) {
    s.Text = cbArray[ r - 1 ];
 
    // Ctrl キーを押しながらのときは、貼り付けしてからアイテムを削除
    if ( $Ctrl() ) {
       cb.ClearData( r - 1 );
      if ( r === 1 ) { cb.ClearData(); }
       Status = " クリップボード履歴からアイテムを削除しました。";
       Status = " クリップボード履歴からアイテムを削除しました。";
     }
     }
    // 貼り付けたデータを履歴の先頭に移動
    else if ( toTop ) {
      cbArray.unshift( cbArray.splice( r - 1, 1 ) );
      for ( var i = 0; i < cbCount; i ++ ) {
        cb.SetData( cbArray[i], i );
      }
      cb.SetData( cbArray[0] );
    }
  }
  // Windows のクリップボードデータから貼り付け
  else if ( r === 17 ) {
    s.Text = cbData;
    // Ctrl キーを押しながらのときは、貼り付けしてからアイテムを削除
    if ( $Ctrl() ) {
      cb.ClearData();
      Status = " クリップボードからアイテムを削除しました。";
    }
  }
  // ¥ を ¥¥ に置換して 履歴の先頭アイテム を貼り付け
  else if ( r === 18 ) {
    s.Text = ( cbData0 || cbData ).replace( /\\/g, "\\\\" );
  }
  // 追加コピー
  else if ( r === 92 ) {
    var line = d.GetLine( s.GetActivePointY( mePosLogical ), 0 );
    var str = s.IsEmpty ? ( line ? line + "\n" : "" )
                        : s.Text;
    if ( str ) {
      var oldData = cbData0 || cbData || "";
      cb.ClearData( 0 );
      cb.SetData( oldData + str );
    }
  }
  // 追加切り取り
  else if ( r === 93 ) {
    if ( s.IsEmpty ) { s.SelectLine(); }
    var str = s.Text;
    if ( str ) {
      var oldData = cbData0 || cbData || "";
      cb.ClearData( 0 );
      cb.SetData( oldData + str );
    }
    s.Delete();
   }
   }
   if ( toTop && ! $ctrl ) {
 
    cbArray.unshift( cbArray.splice( r -1, 1 ) );
 
     for ( var i = 0; i < cbCount; i ++ ) {
   // 99: クリップボード履歴をすべて削除する
       cb.SetData( cbArray[i], i );
  else if ( r === 99
  && Confirm( "クリップボードのすべての履歴を削除しますか? " ) ) {
     for ( var i = cbCount - 1; i >= 0; i -- ) {
       cb.ClearData( i );
     }
     }
     cb.SetData( cbArray[0] );
     cb.ClearData();
    Status = " クリップボード履歴からすべてのアイテムを削除しました。";
   }
   }
}
else if ( r == 17 ) {
  s.Text = cbData ;
}


// 99: クリップボード履歴をすべて削除する
 
else if ( r == 99 && Confirm( confirmStr ) ) {
  // 101 ~ 116: 貼り付けしてからアイテムを削除
  for ( var i = 0; i < cbCount; i ++ ) {
  else if ( r > 100 && r < 200 ) {
     cb.ClearData( i );
    s.Text = cbArray[ r - 101 ];
     cb.ClearData( r - 101 );
    Status = " 履歴からアイテムを削除しました。";
   }
   }
  Status = " クリップボード履歴からすべてのアイテムを削除しました。";
}


// 101 ~ 116: 貼り付けしてからアイテムを削除
else if ( r > 100 && r < 200 ) {
  s.Text = cbArray[ r -101 ];
  cb.ClearData( r -101 );
  Status = " 履歴からアイテムを削除しました。";
}


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


// 300 ~ 316: スニペットに登録(ピン止め)
  // 300 ~ 316: スニペットに登録(ピン止め)
else if ( r >= 300 && r < 400 ) {
  else if ( r >= 300 && r < 400 ) {
  try {
    try {
    var snippetsDir = Fso.GetParentFolderName( snPath );
      // データフォルダに Plugins\\Snippets フォルダがなければフォルダを生成
    var pluginsDir = Fso.GetParentFolderName( snippetsDir );
      var snippetsDir = Fso.GetParentFolderName( snPath );
    if ( ! Fso.FolderExists( pluginsDir ) ) {
      var pluginsDir = Fso.GetParentFolderName( snippetsDir );
       Fso.CreateFolder( pluginsDir );
      if ( ! Fso.FolderExists( pluginsDir ) ) {
        Fso.CreateFolder( pluginsDir );
       }
      if ( ! Fso.FolderExists( snippetsDir ) ) {
        Fso.CreateFolder( snippetsDir );
      }
      // 選択範囲 または クリップボード履歴のアイテム
      var str = ( r === 300 )
              ? st
              : ( cb.GetData( r - 301 ) || cb.GetData() );
      // snippets.txt の末尾に追加登録する
      str = snText
          + ( ( ! snText || snText.charAt( snText.length - 1 ) === "\n" )
            ? "" : "\n" )
          + str.replace( /[\\\t\n\r]/g, function( tmp ) {
              return ( tmp === "\\" ) ? "\\\\"
                  : ( tmp === "\t" ) ? "\\t"
                  : ( tmp === "\n" ) ? "\\n"
                  :/* tmp === "\r" */  "\\r";
            } );
      IO.SaveToFile( snPath, str, "utf-8", true );
      var copyFrom = ( r === 300 ) ? " 選択範囲" : " 選択したアイテム";
      Status = copyFrom + "をスニペットに登録しました。";
     }
     }
     if ( ! Fso.FolderExists( snippetsDir ) ) {
     catch( e ) {
       Fso.CreateFolder( snippetsDir );
       Status = " スニペットに登録できませんでした。";
     }
     }
    // 選択範囲 または クリップボード履歴のアイテム
    var str = ( r == 300 )
            ? st
            : ( cb.GetData( r -301 ) || cb.GetData() );
    str = snText
        + ( ! snText || snText.charAt( snText.length -1 ) == "\n"
          ? "" : "\n" )
        + str.replace( /\\/g, "\\\\" )
            .replace( /\t/g, "\\t" )
            .replace( /\n/g, "\\n" )
            .replace( /\r/g, "\\r" );
    IO.SaveToFile( snPath, str, "utf-8", true );
    var copyFrom = ( r == 300 ) ? "範囲" : "したアイテム";
    Status = " 選択" + copyFrom + "をスニペットに登録しました。";
  } catch( e ) {
    Status = " スニペットに登録できませんでした。";
   }
   }
}


// 400: スニペットを編集(snippets.txt を開く)
else if ( r == 400 ) {
  WshShell.Run( "\"" + editor.FullName + "\" \"" + snPath + "\"" );
  Status = " " + snPath;
}


// 401 ~ : ピン止めアイテムを貼り付け
  // 400: スニペットを編集(snippets.txt を開く)
else if ( r > 400 ) {
  else if ( r === 400 ) {
  var str = snArray[ r -401 ].replace( /^(?:\t)*[^\t]*\t/, "" )
    WshShell.Run( "\"" + meryPath + "\" \"" + snPath + "\"" );
                            .split( "\\\\" );
    Status = " " + snPath;
  for ( var i = 0, len = str.length; i < len; i ++ ) {
    str[i] = str[i].replace( /\\t/g, "\t" )
                  .replace( /\\r/g, "\r" )
                  .replace( /\\n/g, "\n" )
                  .replace( /\\/g, "" );
   }
   }
   str = str.join( "\\" );
 
  // Ctrl キーを押しながらのときは、貼り付けしたアイテムをコピー
 
  if ( gksIsExist ) {
  // 401 ~ : ピン止めアイテムを貼り付け
    $ctrl = WshShell.Run( "\"" + gksPath + "\" c", 0, true );
  else if ( r > 400 ) {
    if ( $ctrl == 1 ) {
    var $ctrl = $Ctrl();
       cb.SetData( str );
    var snStr = snArray[ r - 401 ].replace( /^(?:\t)*[^\t]*\t/, "" )
       Status = " クリップボードにアイテムをコピーしました。";
                                  .split( "\\\\" );
    for ( var i = 0, len = snStr.length; i < len; i ++ ) {
      snStr[i] = snStr[i].replace( /\\t|\\r|\\n|\\/g, function( tmp ) {
        return ( tmp === "\\t" ) ? "\t"
            : ( tmp === "\\r" ) ? "\r"
            : ( tmp === "\\n" ) ? "\n"
            :/* tmp === "\\" */   "";
      } );
    }
    snStr = snStr.join( "\\" );
 
    // ピン止めアイテムで選択範囲を囲う
    var ham3 = ham + ham + ham,  ham4 = ham3 + ham;
    var reg = /\n?$/,  n = st.match( reg );
    var str = st.replace( reg, "" );
 
    // "@@@@" で区切られた前後の文字列で選択範囲の各行を囲う
    if ( sandwich && snStr.indexOf( ham4 ) > -1 ) {
      var a = str.split( "\n" );
      for ( var i = 0, len = a.length; i < len; i ++ ) {
        a[i] = snStr.replace( RegExp( ham4, "g" ), a[i] );
      }
      // クリップボード経由で「貼り付け」
      cb.SetData( str = a.join( "\n" ) + n );
      s.Paste();
      // クリップボードデータを復帰
      cb.ClearData();
      for ( var i = 0; i < 16; i ++ ) {
        cb.SetData( cbArray[i] || "", i );
      }
      cb.SetData( cbArray[0] );
      if ( $ctrl ) { cb.SetData( str ); }
    }
 
    // "@@@" で区切られた前後の文字列で選択範囲全体を囲う
    else if ( sandwich && snStr.indexOf( ham3 ) > -1  ) {
      s.Text = str = snStr.replace( RegExp( ham3, "g" ), str ) + n;
       if ( $ctrl ) { cb.SetData( str ); }
    }
 
    // ピン止めアイテムを貼り付け
    else {
       s.Text = snStr;
      // Ctrl キーを押しながらのときは、貼り付けたアイテムをコピー
      if ( $ctrl ) {
        cb.SetData( snStr );
        Status = " クリップボードにアイテムをコピーしました。";
      }
     }
     }
   }
   }
  s.Text = str;
 
}
} // main:{}




474行目: 813行目:
     } )
     } )
   };
   };
   editorVer = + ( Pad2( editor.Version ).replace( /\./g, "" ).slice( 0, 6 ) );
   editorVer = Pad2( editor.Version ).replace( /\./g, "" ).slice( 0, 6 );
   requirement = + ( Pad2( versionStr ).replace( /\./g, "" ).slice( 0, 6 ) );
   requirement = Pad2( versionStr ).replace( /\./g, "" ).slice( 0, 6 );
   return ( editorVer >= requirement );
   return ( Number( editorVer ) >= Number( requirement ) );
}
}


/**
/**
  * 関数 MenuKey( str, num, numWidth, menuWidth, boolean )
  * 関数 MenuKey( str, num, numWidth, menuWidth, blankChr, boolean )
  * ポップアップメニューに表示するラベルを生成する
  * ポップアップメニューに表示するラベルを生成する
  * ・行頭空白を除去、空白文字を圧縮: → 「›」(U+203A)
  * ・行頭空白を除去、空白文字を圧縮:       → 「›」(U+203A)
  * ・改行記号を可視化: → 「↲」(U+21B2) または 「⏎」(U+23CE)
  * ・改行記号を可視化:                     → 「↲」(U+21B2)
  * ・削られてしまう 「&」 を補完
  * ・削られてしまう 「&」 を補完
  * ・「¥」(U+005C) を 「∖」(U+2216) に置換: または 「╲」(U+2572), 「﹨」(U+FE68)
  * ・「¥」(U+005C) を 「∖」(U+2216) に置換:     → 「╲」(U+2572)
  * ・ゼロ幅や特殊な空白文字を豆腐に置換: → 「⊠」(U+22A0) または 「▯」(U+25AF)
  * ・ゼロ幅や特殊な空白文字を豆腐に置換:   → 「▯」(U+25AF)
  * ・判別しづらい半角記号を全角に置換: !"%'(),.:;@[]`{|}
  * ・判別しづらい半角記号を全角に置換:     !"%'(),.:;@[]`{|}
  *    + 「a-z」 を全角に置換
  *    + 「a-z」 を全角に置換
  *  または半角記号の前後にスキマをつける: HAIR SPACE 「 」(U+200A)
  *  または半角記号の前後にスキマをつける: HAIR SPACE 「 」(U+200A)
  * ・行番号を空白でケタ埋め: EN SPACE 「 」(U+2002)
  * ・行番号を空白でケタ埋め:
  * ・文字数を切り詰め
  * ・menuWidth を目安に文字数を切り詰め
  */
  */
function MenuKey( str, num, numWidth, menuWidth, conv ) {
function MenuKey( str, num, numWidth, menuWidth, b1, conv ) {
   var keyWidth = menuWidth;
   var keyWidth = menuWidth;
   if ( conv ) {
   if ( conv ) {
     var strArr = str.replace( /^[^\t]*\t/, "" ).split( "\\\\" );
     var strArr = str.replace( /^[^\t]*\t/, "" ).split( "\\\\" );
     for ( var i = 0; i < strArr.length; i ++ ) {
     for ( var i = 0; i < strArr.length; i ++ ) {
       strArr[i] = strArr[i].replace( /\\t/g, " › " )
       strArr[i] = strArr[i].replace( /\\t/g, " \u203A " ) //「›」
                           .replace( /\\r|(\\r)?\\n/g, " " )
                           .replace( /\\r|(\\r)?\\n/g, " \u21B2 " ) //「↲」
                           .replace( /\\/g, "" );
                           .replace( /\\/g, "" );
     }
     }
505行目: 845行目:
   }
   }
   var reg = /[\u00A0\u1680\u180E\u2000-\u200E\u2028\u2029\u202F\u205F\u2062\u2063\uFEFF]/g;
   var reg = /[\u00A0\u1680\u180E\u2000-\u200E\u2028\u2029\u202F\u205F\u2062\u2063\uFEFF]/g;
   var menuKey = str.replace( /(?:\t|[ ]{3,}|[ ]{2,})+/g, " › " )
   var key = str.replace( /(?:\t|[ ]{3,}|[ ]{2,})+/g, " \u203A " ) //「›」
                  .slice( 0, menuWidth *2)
              .slice( 0, menuWidth *2)
                  .replace( /(?:\r?\n|\r)/g, " " ) // ⏎ ↵
              .replace( /(?:\r?\n|\r)/g, " \u21B2 " ) //「↲」
                  .replace( /[&]/g, "&&" )
              .replace( /[&]/g, "&&" )
                  .replace( /[\\]/g, "" )
              .replace( /[\\]/g, "\u2216" )           //「∖」
                  .replace( reg, "" );
              .replace( reg, "\u25AF" );               //「▯」
   if ( toFullWidth ) {
   if ( toWideWidth === 2 ) {
     menuKey = menuKey.replace( /[!"%'(),.:;@\[\]`a-z{|}]/g,
     key = key.replace( /[!"%'(),.:;@\[\]`a-z{|}]/g, function( tmp ) {
                function( tmp ) {
      return String.fromCharCode( tmp.charCodeAt( 0 ) + 0xFEE0 )
                  return String.fromCharCode( tmp.charCodeAt( 0 ) + 0xFEE0 )
    } );
                } );
   }
   }
   else {
   else if ( toWideWidth ) {
     // スラッシュ 以外の ascii 記号と fijl に HAIR SPACE (U+200A) を付加
     // スラッシュ 以外の ascii 記号と fijl に HAIR SPACE (\u200A) を付加
     // [!"#$%'()*+,-.:;<=>?@\[\]^_`{|}~] + "&&"
     // [!"#$%'()*+,-.:;<=>?@\[\]^_`{|}~] + "&&"
     menuKey = menuKey.replace( /[!-%'-.:-@\[-`{-~fijl¥⊠▯]|&&/g, "$&" )
     key = key.replace( /[!-%'-.:-@\[-`{-~fijl\u00A5\u22A0\u25AF]|&&/g
                    .replace( /\u200A+/g, "" )
                    , "\u200A$&\u200A" ) //「¥」「⊠」「▯」
                    .replace( /\u2002\u200A/g, "" );
            .replace( /\u200A+/g, "\u200A" )
     keyWidth = keyWidth
            .replace( RegExp( b1 + "\u200A", "g" ), b1 );
            + ( menuKey.match( /[\u200A›↲]|&&/g ) || "" ).length;
    // HAIR SPACE,「›」,「↲」
     keyWidth += ( key.match( /[\u200A\u203A\u21B2]|&&/g ) || "" ).length;
   }
   }
   num = ( "   " + num ).slice( - numWidth ).replace( /\d$/, "&$&:" );
   num = num.PadSpace( numWidth ).replace( /\d$/, "&$&:" + b1 );
   menuKey = ( menuKey.length > keyWidth )
   key = ( key.length > keyWidth )
          ? menuKey.slice( 0, keyWidth ) + " ..."
      ? key.slice( 0, keyWidth ) + " ..." : key;
          : menuKey;
   return num + key;
   return num + menuKey;
}
}


// ---------- ▼ 能書き ▼ ---------- //


/**
/**
  【このマクロの仕様・制限事項】
  * 関数 GetTag( tagType, tagKey, property )
・ver 2.8.1 以前の Mery では「クリップボード履歴」機能を使用できません。
  * 指定された Tag の値を返す
・「クリップボード履歴」は Mery 本体のメモリストアのものを使用します。
・「スニペット」プラグインの設定ファイル snippets.txt の読み書きをします。
  ※「include ライブラリ」が必要です(snippets.txt ファイルの読み書きに使用)。
  ※「スニペット」プラグインを導入していない場合でも、
    「ピン止めアイテム/スニペット」機能を使用できます。
    snippets.txt ファイルの保存場所は Mery\Plugins\Snippets フォルダです
  (インストーラ版では %AppData%\Mery\Plugins\Snippets フォルダ)。
・「GetKeyState.exe(キー状態取得実行ファイル) 」で機能を拡張できます。
      → クリップボード履歴のアイテムを Ctrl+クリックした場合、
        アイテムを貼り付けし、貼り終えたアイテムを削除します。
        また、ピン止めアイテムを Ctrl+クリックした場合、
        アイテムを貼り付けし、そのアイテムをクリップボードにコピーします。
 
  ※「GetKeyState.exe」は Mery\Macros フォルダに配置してください
    (Macros フォルダ以外の場所にあるなら設定項目でパスを指定する)。
    (Macros フォルダ以外の場所にあるなら設定項目でパスを指定する)。
  ※「GetKeyState.exe」がなくても、クリップボード履歴の
      サブメニュー項目から「貼り付けしてからアイテムを削除」できます。
【ツールメニューの「クリップボード履歴」と異なる部分】 (2019/11/05)
 
・「クリップボードのすべての履歴を削除」したあとでも、
  Windows OS のクリップボードの最新の1件がテキストデータである場合は、
  「クリップボード履歴」にアイテムが表示されます。
  ※ Mery 本来の「クリップボード履歴」機能でも「履歴を消去」したあとにも
    Ctrl+V によるペーストができることがあるので、これを可視化してあります。
【スニペットプラグインと異なる部分】 (2019/11/27)
 
・ このマクロからスニペットに登録したアイテムは、
    snippets.txt の末尾に追加されます。
 
・ snippets.txt 内の 「-」×1 と空白だけの行をセパレータにしません。
      → メイン階層を「クリップボード履歴」と共用しているため、
        体裁上の都合で「ピン止めアイテム」内でのセパレータは見送り。
  ※ 「-」×2 以上であれば、有効文字列として扱います。
  ※「ピン止めアイテム/スニペット」サブメニュー内では
      階層ごとに区切ってベタで列挙します。
 
・ snippets.txt 内で、行頭が "「-」×1 + タブ文字" ではじまる行を無視します。
      → 行頭が "「-」 +タブ文字" の行は "コメントアウトされた行" と見做します。
  ※ スニペットプラグインではタブ文字のあとに文字列があれば
      「-」 をラベルとしてメニューのアイテムに追加しますが、
      このマクロでは無効な行となります。
 
  ・ snippets.txt 内の 「&」 記号によるアクセラレータはすべて無視します。
      → 有効文字列のある行を上から順に連番化し、番号をアクセラレータにします。
 
・ snippets.txt 内の「空行・空白行」の扱い方などで
    スニペットプラグインと異なる解釈をしている部分があります。
  ※ このマクロでは、snippets.txt 内の 空行、タブ文字だけの空白行、「-」 だけの行を
      完全に無視するので、「空文字や "-" だけのサブメニュー見出し」を作りません
    (ただし、全角空白や2つ以上のハイフン 「--」 は文字列として扱う)。
        → ポップアップメニューでの表示状態がスニペットプラグインと
          同じ階層構造にならないことがあります。
  ※ snippets.txt 内の空行や 「-」 だけの行、タブインデントでのクループ化が
      適切でない部分は、ポップアップメニューに正しく反映されません。
 
・ タブインデントの階層を深くするさいは、かならず一段ずつ下げてください
  (二段以上の差があるとエラーの元)。
      → その行またはグループをスキップし、ポップアップメニューに表示しません。
  ※ 階層を浅くするさいは、二段以上の差があっても構いません。
  */
  */
</source>
function GetTag( tagType, tagKey, property ) {
 
  try {
 
    var obj = ( typeof tagType === "object" ) ? tagType
== 追加コード:「n 番目から m 番目まで貼り付け」 ==
            : ( tagType === 1 ) ? editor.ActiveDocument
(2020/05/02)<br>
            : ( tagType === 2 ) ? editor
履歴内の連続する複数のアイテムを '''まとめて貼り付け''' できるようにするための追加コードです。
            : ( tagType === 3 ) ? window
<br>
            : window;
* 「n 番目から m 番目まで貼り付け」
    return ( obj.Tag.Exists( tagKey )
: 指定した複数のアイテムを '''そのままつなげて''' 貼り付けます。
    && ( property ? property in obj.Tag( tagKey ) : true ) )
* 「n 番目から m 番目まで改行つなぎで貼り付け」
    ? property
: 指定した複数のアイテムを '''改行でつなげて''' 貼り付けます。
      ? obj.Tag( tagKey )[ property ]
* 「n 番目から m 番目まで空行つなぎで貼り付け」
      : obj.Tag( tagKey )
: 指定した複数のアイテムを '''空行をはさんでつなげて''' 貼り付けます。
    : null;
<br>
   } catch( e ) { Status = e;  return null; }
追加コマンドを選択すると入力ダイアログが2回表示されますので、ポップアップメニュー内での履歴アイテムの番号を指定してください。<br>
※ コピーした順番どおりにつなげて貼り付けするばあいは、「さいしょのアイテムの番号」'''>'''「さいごのアイテムの番号」です。<br>
 e.g. 最新の3件をコピーした順につなげて貼り付けするなら
:「さいしょのアイテムの番号」= '''3'''
:「さいごのアイテムの番号」= '''1'''
※ 入力ダイアログが 空の状態/無効な文字列のみが入力された状態 でキャンセルされたばあいは、なにも貼り付けしません。
<br><br>
※ 追加設定項目 <syntaxhighlight lang="javascript" inline>var unitToTop = true;</syntaxhighlight> にすると、つなげたテキストデータをクリップボード履歴の先頭に追加登録します (ただし 16 件目にあったアイテムは消えます)。<br>
* <syntaxhighlight lang="javascript" inline>var unitToTop = true;</syntaxhighlight> でも <syntaxhighlight lang="javascript" inline>var unitToTop = false;</syntaxhighlight> でも、つなげる前の各アイテムは削除しません (削除する仕様だと番号指定をまちがえたときに手戻りできなくなってしまう)。
* クリップボード履歴にアイテムを累積させない「[[#「追加コピー」マクロ|追加コピー]]」マクロを用意しました。
<br><br>
----
以下に指定する3ヶ所([[#ソースコード|ソースコード]] のプレビューでハイライト表示されている部分)に 挿入 または '''上書き''' します。
<br><br>
; ソースコード 22 行目付近に挿入 (設定項目を追加)
<source lang="javascript">
// ■ toTop = true のとき (◆2020/05/02 追加)
//  「n 番目から m 番目まで貼り付け」したアイテムを
//    クリップボード履歴の先頭に登録する(※ただし 16 番目だったアイテムは消える)
var unitToTop = false; // true: 登録する / false: 登録しない
 
</source>
<br><br>
; ソースコード 240 - 242 行目 を上書き
* <syntaxhighlight lang="javascript" inline>if ( cbCount > 1 ) { ... }</syntaxhighlight> を追加するだけですが、挿入箇所を明確にする意味で既存のコード部分に <q>上書き</q> としておきます。
<source lang="javascript">
// (◆2020/05/02 追加)
if ( cbCount > 1 ) {
  menu.Add( "", 0, meMenuSeparator );
  menu.Add( "n 番目から m 番目まで貼り付け (&M)", 100, grayFlag );
  menu.Add( "n 番目から m 番目まで改行つなぎで貼り付け (&N)", 200, grayFlag );
   menu.Add( "n 番目から m 番目まで空行つなぎで貼り付け (&B)", 300, grayFlag );
}
}


menu.Add( "", 0, meMenuSeparator ); // 既存のコード
/**
menu.Add( "キャンセル & ", 0 ); // 既存のコード
* 関数 SetTag( value, tagType, tagKey, property )
</source>
* 指定された値を Tag に書き込む
<br><br>
*/
; ソースコード 275 - 277 行目 を上書き
function SetTag( value, tagType, tagKey, property ) {
* 既存のコードの <syntaxhighlight lang="javascript" inline>else if</syntaxhighlight> の行の変更が必要なので、以下のコードで上書きしてください。
   try {
<source lang="javascript">
     var obj = ( typeof tagType === "object" ) ? tagType
            : ( tagType === 1 ) ? editor.ActiveDocument
// 100, 200, 300: n 番目から m 番目まで貼り付け (◆2020/05/02 追加)
            : ( tagType === 2 ) ? editor
unit:
            : ( tagType === 3 ) ? window
if ( r == 100 || r == 200 || r == 300 ) {
            : window;
   // 半角変換した ascii 文字列を返す関数
     if ( property ) {
  var ToHalfWidth = function( strVal ) {
       if ( obj.Tag.Exists( tagKey ) ) {
     return strVal.replace( /[!-~]/g, function( tmp ) {
         obj.Tag( tagKey )[ property ] = value;
        return String.fromCharCode( tmp.charCodeAt(0) - 0xFEE0 )
  } ) };
  // 入力ダイアログ×2回 ※全角/半角の数字 のみを有効文字列として取得する
  var p1 = + ToHalfWidth( Prompt( "さいしょのアイテムの番号", "" ) )
                        .replace( /\D/g, "" );
  if ( ! ( p1 > 0 && p1 <= cbCount ) ) { Status = " キャンセル";  break unit; }
  var p2 = + ToHalfWidth( Prompt( "さいごのアイテムの番号", p1 ) )
                        .replace( /\D/g, "" );
  if ( ! ( p2 > 0 && p2 <= cbCount ) ) { Status = " キャンセル";  break unit; }
 
  if ( p1 != p2 ) {
    var pStr = "";
    var br = ( r == 200 ) ? "\n" : ( r == 300 ) ? "\n\n" : "";
    // 上から順に貼り付け
     if ( p1 < p2 ) {
       for ( var i = p1; i <= p2; i ++ ) {
         pStr += cbArray[ i - 1 ] + br;
      }
    }
    // 下から順に貼り付け
    else if ( p1 > p2 ) {
      for ( var i = p2; i <= p1; i ++ ) {
        pStr = cbArray[ i - 1 ] + br + pStr;
       }
       }
      else { obj.Tag( tagKey ) = { property: value }; }
     }
     }
     // 貼り付け
     else { obj.Tag( tagKey ) = value; }
    s.Text = pStr;
 
    // まとめたデータを履歴の先頭に登録
    if ( toTop && unitToTop ) { cb.SetData( pStr ); }
   }
   }
 
   catch( e ) { Status = e; }
   // 入力された番号が同一なら、通常の貼り付けコマンド if ( r <= 16 ) に飛ぶ
   finally { return; }
   else /* if ( p1 === p2 ) */ { r = p1; }
}
}
// 1 ~ 17: クリップボード履歴のアイテムを貼り付け
// else // (◆2020/05/02: else if を途中で区切って、else のみコメントアウト)
if ( r <= 16 ) { // 既存のコード
</source>


== 「追加コピー」マクロ ==
(2020/05/02)<br>
すでにマクロライブラリ内の「[[キーアサイン集]]」のページに「[[キーアサイン集#追加コピー|追加コピー]]」マクロがありますが、クリップボード履歴に「追加コピー」前/後のアイテムが累積されていき、ばあいによっては履歴を無駄に圧迫することになってしまいます。<br>
以下のコードは履歴の2番目以降のアイテムを圧迫せずに、最新のデータだけを「追加コピー」の結果に置きかえるものです。
* '''この「追加コピー」マクロは <span style="color:#c00;"> Mery 2.8.1 以降 </span> でしか利用できません。'''
* たぶん Windows OS のクリップボードには「追加コピー」前/後のデータが累積されていきます…。(未確認)
<source lang="javascript">
#title = "追加コピー"
#tooltip = "追加コピー"
// #icon = "Mery用 マテリアルデザインっぽいアイコン.icl",214


/**
/**
  * 「追加コピー」マクロ / Mery ver 2.8.1 以降用
  * 関数 AddStrPrompt( tagType, tagKey )
* (sukemaru, 2020/04/23 - 2020/04/25)
  * 「任意の文字列」コマンド
  * ※ クリップボード履歴に累積させずに、最新のデータに上書きで追加コピーする
  * 入力ダイアログで指定された文字列を返す
  * (履歴上のもっとも古い16件目のデータを保持)
  */
  */
function AddStrPrompt( tagType, tagKey ) {
  // 前回使用した文字列があればダイアログの初期値に再利用
  var str = "";
  if ( tagType && ( t = GetTag( tagType, tagKey, "addStr" ) ) ) {
    str = t;
  }
  // 入力ダイアログ
  var msg = "連結用文字列:\t"
          + "改行 = \\\\\\n ; タブ = \\\\\\t  (注:¥記号3つ)";
  p = Prompt( msg, str ) || "";
  if ( p && tagType ) {
    SetTag( p, tagType, tagKey, "addStr" );
  }
  return p.replace( /\\\\\\t/g, "\t" ).replace( /\\\\\\n/g, "\n" );
}
</syntaxhighlight>
== 変更履歴 ==
<div style="height:60em; overflow:auto;">
• 2020-06-27
 ・GetKeyState.exe による機能拡張の追加
  (クリップボード履歴アイテムの Ctrl+クリックで ¥ を ¥¥ に置換して貼り付け)
 ・「追加コピー」「追加切り取り」コマンドを追加
 ・「n 番目から m 番目まで貼り付け」コマンドを追加
 ・「n 番目から m 番目まで貼り付け」したデータのまとまりを履歴の先頭に登録する設定項目を追加
 ・「ピン止めアイテムで選択範囲を囲う」機能と設定項目を追加
 ・連番数字のケタ埋め用の空白文字のカスタマイズ用設定項目を追加
 ・ソースコード内の「能書き (詳細説明)」を削除
 ・ソースコード内の unicode 文字をコードポイント \uHHHH に書き換え


var d = editor.ActiveDocument, s = d.selection;
  • 2020-06-05
var line = d.GetLine( s.GetActivePointY( mePosLogical ), 0 );
   ・「追加コピー」マクロの節を削除<br> ⇒「[[追加コピー・追加切り取り]]」マクロ(※ Mery Ver 2.8.1 以降用)のページを新設
var str = s.IsEmpty ? ( line ? line + "\n" : "" ) : s.Text;
if ( str ) {
  var cb = ClipboardData;
  var oldData = cb.GetData( 0 ) || cb.GetData() || "";
  cb.ClearData( 0 );
  // cb.ClearData(); // OS 側の履歴にも累積させないようにするならこの行が必用かも...?
  cb.SetData( oldData + str );
}
</source>


• 2020-05-02
 ・[[追加コード:「n 番目から m 番目まで貼り付け」|「n 番目から m 番目まで貼り付け」]] を追加掲載([[#ダウンロード|ダウンロード]]の ZIP 書庫のソースコードは変更していません)
 ・「追加コピー」マクロの節を追加(ZIP 書庫には未収録)


== 更新履歴 ==
: ここまでで、だいたいはスニペットの機能を再現できたとおもいます…。
• 2019/08/01: 初版


  • 2019/08/06:
  • 2019-11-29
   ・「ピン止めアイテムを貼り付け」コードの変数ミスを修正
   ・「半角英文字を全角で表示」しない設定のとき、ascii 記号の前後に HAIR SPACE
 ・クリップボード履歴内の重複アイテムを削除する設定を追加。
   ・snippets.txt 内で、行頭が "「-」×1 + タブ文字" の行を無視
 ・ピン止めアイテムが少ないときはメインメニューにアイテムを表示。
 ・クリップボード履歴から貼り付けたアイテムを、履歴の先頭に移動させるオプションを追加
   ・snippets.txt 内の空行を無視。
   ・ピン止めアイテムを Ctrl+クリックした場合、アイテムを貼り付けし、そのアイテムをクリップボードにコピー
   ・クリップボード履歴に長大なデータがあるときの動作速度を少しだけ改善。


  • 2019/11/01:
  • 2019-11-09
   ・ステータス表示を追加
   ・Ver 2.8.0 以前の Mery でもスニペット機能だけ利用できるように変更
   ・クリップボードにテキストデータがないときのメニュー構成を変更
   ・「クリップボード履歴の重複アイテム削除」の設定項目を廃止( "削除する" で固定)
   ・アイテムの行数(改行数+1)をメニュー内に追加表示
   ・snippets.txt 内の単独「\」の処理をスニペットプラグインにあわせる修正
 ・メニュー内に表示する改行コードの矢印を「↲」(U+21B2) に変更
   ・snippets.txt 内のセパレータ用の「-」の行をスキップする処理を追加
 ・スニペット機能での「ピン止め/貼り付け」のさいの、改行コードやタブコードではない文字列「\n」や「\t」の扱いを修正
   ・snippets.txt がないときのエラーを修正


  • 2019/11/05
  • 2019-11-05
   ・各アイテムの行数表示を廃止
   ・各アイテムの行数表示を廃止
   ・クリップボード履歴からスニペットにアイテムを登録するコマンドを追加
   ・クリップボード履歴からスニペットにアイテムを登録するコマンドを追加
769行目: 982行目:
    ( … つもりだが、アレンジしたので同じ表示状態にならないことがある)
    ( … つもりだが、アレンジしたので同じ表示状態にならないことがある)


  • 2019/11/09
  • 2019-11-01:
   ・ver 2.8.0 以前の Mery でもスニペット機能だけ利用できるように変更
 ・ステータス表示を追加
   ・「クリップボード履歴の重複アイテム削除」の設定項目を廃止( "削除する" で固定)
   ・クリップボードにテキストデータがないときのメニュー構成を変更
   ・snippets.txt 内の単独「\」の処理をスニペットプラグインにあわせる修正
 ・アイテムの行数(改行数+1)をメニュー内に追加表示
   ・snippets.txt 内のセパレータ用の「-」の行をスキップする処理を追加
   ・メニュー内に表示する改行コードの矢印を「↲」(U+21B2) に変更
   ・スニペット機能での「ピン止め/貼り付け」のさいの、改行コードやタブコードではない文字列「\n」や「\t」の扱いを修正
   ・snippets.txt がないときのエラーを修正


  • 2019/11/29
  • 2019-08-06:
   ・「半角英文字を全角で表示」しない設定のとき、ascii 記号の前後に HAIR SPACE
 ・「ピン止めアイテムを貼り付け」コードの変数ミスを修正
   ・snippets.txt 内で、行頭が "「-」×1 + タブ文字" の行を無視
 ・クリップボード履歴内の重複アイテムを削除する設定を追加
 ・クリップボード履歴から貼り付けたアイテムを、履歴の先頭に移動させるオプションを追加
   ・ピン止めアイテムが少ないときはメインメニューにアイテムを表示
   ・ピン止めアイテムを Ctrl+クリックした場合、アイテムを貼り付けし、そのアイテムをクリップボードにコピー
   ・snippets.txt 内の空行を無視
: これでだいたいはスニペットの機能を再現できたとおもいますが…。
   ・クリップボード履歴に長大なデータがあるときの動作速度を少しだけ改善


  • 2020/05/02
  • 2019-08-01: 初版
 ・[[追加コード:「n 番目から m 番目まで貼り付け」|「n 番目から m 番目まで貼り付け」]] を追加掲載([[#ダウンロード|ダウンロード]]の ZIP 書庫のソースコードは変更していません)
</div>
 ・[[#「追加コピー」マクロ|「追加コピー」マクロ]] を追加(ZIP 書庫には未収録)

2024年9月9日 (月) 11:37時点における最新版

概要[編集]

「クリップボード履歴」と「スニペット (テンプレート)」機能とをひとまとめにしたマクロです。

  • 2in1+α の機能をひとつのツールバーアイコンから利用できます。
  • 「クリップボード履歴」と「スニペット」間のデータの受け渡しができるようになります。
  • 「クリップボード履歴」機能は Mery 2.8.1 以降で利用できます。
  • 「スニペット」機能は スニペットプラグイン の設定ファイル snippets.txt を読み書きするというかたちにしています。
  • 「クリップボード履歴」機能は Mery 2.8.1 以降 でしか利用できません。
  • あらかじめ「includeライブラリ」の導入が必要です。
  • 外部実行ファイル「GetKeyState.exe」で機能を拡張できます(なくても差し支えありません)。
  • スニペットプラグイン」を導入していないでも「スニペット(定型文)」機能を利用できます。
snippets.txt 内のタブインデントによる階層構造をある程度ポップアップメニューに反映させられるようになっています。
ref. 「スニペットプラグイン」のページの Snippets.txt の書き方 を参照のこと。
※ ただし、このマクロでは snippets.txt 内の空行などの扱いについて 「スニペットプラグイン」と異なる部分 があります。
  • 動作確認は Windows XP sp3 (32bit) × Mery ベータ版 2.8.6 以降(ポータブル版)でしかしていません。
なにかしらの支障を来たす不具合が見つかった場合は、このマクロを削除して使用を中止するか、または フォーラム にてご報告ください。
cf. マクロライブラリには「定型文を挿入」機能に特化したマクロも別途あります。

ツールメニューの「クリップボード履歴」と異なる部分[編集]

(2019-11-29)

  • オプション設定(toTop)により、貼り付けたアイテムを「履歴の先頭に移動する」ことができます。
⇒ 以後、Ctrl+V でつづけて貼り付けできるようになります。
  • クリップボード内に重複するアイテム(完全におなじ文字列データ)がある場合、ひとつを残して古いデータを削除します。
⇒ 履歴 16 件分、無駄なく活用できます。
※ 重複データを削除するのはこのマクロを実行したときだけです。 通常の「コピー」操作を監視するものではありません。
  • クリップボードのすべての履歴を削除」 「履歴からアイテムをひとつ削除」 「貼り付けしてからアイテムを削除」 「履歴からアイテムをスニペットに登録」 の機能を追加してあります。
ポップアップメニュー内のアイテムの Ctrl+クリック で「貼り付けしてからアイテムを削除」する機能は、「GetKeyState.exe 」を導入している場合にかぎり利用できます。

(2020-06-27 追加)

  • クリップボード履歴内の複数のアイテムをまとめて貼り付け(つなげて貼り付け)できます。
  • 追加コピー/追加切り取り」コマンドを利用できます(キーアサイン集バージョンとは異なり「追加コピー/追加切り取り」前後のアイテムがクリップボード履歴に累積しません)。
ref. 「追加コピー・追加切り取り」マクロ
  • GetKeyState.exe 」を導入していれば、アイテムを Ctrl+クリック したときに、アイテムを貼り付けし、そのアイテムをクリップボード履歴から削除します。
※ 設定項目 var ctrlEscape = true)にすると、¥ 記号を含むアイテム(ファイルパスなど)を Ctrl+クリック したときに ¥ を二重(¥¥)にして貼り付けます。

「スニペットプラグイン」と異なる部分[編集]

(2019-11-29)

  • ピン止めアイテムを Ctrl+クリック した場合、アイテムを貼り付けし、そのアイテムをクリップボードにコピーします(GetKeyState.exe 」を導入している場合のみ)。
⇒ 以後、Ctrl+V でつづけて貼り付けできるようになります。
  • このマクロからスニペットに登録したアイテムは、snippets.txt の末尾に追加されます。
  • snippets.txt 内の & 記号によるアクセラレータをすべて無視します。
⇒ 有効文字列のある行を上から順に連番化し、番号をアクセラレータにします。
  • snippets.txt 内の "半角ハイフン - ひとつ + 空白だけの行" をセパレータにしません。
(ポップアップメニューのメイン階層を「クリップボード履歴」と共用しているため、体裁上の都合でメインメニュー上でのセパレータ付加は見送り)
-×2 以上であれば、有効文字列として扱います (プラグインでは、これもセパレータになる)。
※「ピン止めアイテム/スニペット」サブメニュー内では、階層ごとに区切って全アイテムを列挙します。
  • snippets.txt 内の「空行・空白行」の扱い方などでスニペットプラグインと異なる解釈をしている部分があります。
    このマクロでは、snippets.txt 内の「空行」と「タブ文字/半角空白だけの空白行」を完全に無視するので、「空文字のサブメニュー見出し」を作りません (ただし、全角空白や2つ以上のハイフン -- は有効な文字列として扱います)。
⇒ ポップアップメニューでの表示状態がスニペットプラグインと同じ階層構造にならないことがあります。
  • snippets.txt 内で、行頭が "-×1 + タブ文字" ではじまる行を無視します。
⇒ 行頭が "-+タブ文字" の行は "コメントアウトされた行" と見做します。
※ スニペットプラグインではタブ文字のあとに文字列があれば - をラベルとしてメニューのアイテムに追加しますが、このマクロでは無効な行となります。
  • snippets.txt 内の空行やタブインデントでのクループ化が適切でない部分は、ポップアップメニューに正しく反映されません (その行または段落/グループをスキップし、ポップアップメニューに表示しません)。
※ タブインデントの階層を深くするさいは、かならず一段ずつ下げてください。
二段以上の差があるとエラーの元になったり、または予期せぬ階層に表示されたりします (これはスニペットプラグインでも起きえるものであり、不具合ではありません)。
※ 階層を浅くするさいは、二段以上の差があっても構いません。

(2020-06-27 追加)

  • オプション設定により snippets.txt 内で @@@ または @@@@ を含むアイテムの場合、「選択範囲を囲う」ことができます。

n 番目から m 番目まで貼り付け[編集]

クリップボード履歴内の連続する複数のアイテムを まとめて貼り付け します。

  • 「n 番目から m 番目まで貼り付け」
指定した複数のアイテムを そのままつなげて 貼り付けます。
  • 「n 番目から m 番目まで 任意の文字列 でつないで貼り付け...」
指定した複数のアイテムを 入力ダイアログで指定した文字列 でつなげて貼り付けます。
  • 「n 番目から m 番目までを 改行 でつないで貼り付け」
指定した複数のアイテムを 改行でつなげて 貼り付けます。

※ 「n 番目から m 番目までを 半角空白 でつないで貼り付け」「n 番目から m 番目までを 空行 でつないで貼り付け」コマンドもあります(ソースコード内 340 行目付近でコメントアウト)。

追加コマンドを選択すると入力ダイアログが2回(「任意の文字列」では3回)表示されますので、ポップアップメニュー内での 履歴アイテムの番号 を指定してください。

※ コピーした順番どおりにつなげて貼り付けするばあいは、「さいしょのアイテムの番号」>「さいごのアイテムの番号」です。

 e.g. 最新の3件をコピーした順につなげて貼り付けするなら

「さいしょのアイテムの番号」=
「さいごのアイテムの番号」=

※ 入力ダイアログが "空の状態" または "無効な文字列のみが入力された状態" でキャンセルされたばあいは、なにも貼り付けしません。

※ 設定項目 var unitToTop = true; にすると、つなげたテキストデータをクリップボード履歴の先頭に追加登録します (ただし 16 件目にあったアイテムは消えます)。
var unitToTop = true; でも var unitToTop = false; でも、つなげる前の各アイテムは削除しません (削除する仕様だと番号指定をまちがえたときに手戻りできなくなってしまう)。

ピン止めアイテムで選択範囲を囲う[編集]

設定項目 var sandwich = true にすると、snippets.txt 内で @@@ または @@@@ を含むアイテムは「選択範囲の囲い込み」に利用できるようになります。

囲い込み用文字列が改行を含んでいる場合、マルチカーソル/複数選択に非対応です(正しく囲い込みできません)。

  • @3つ@@@)ふくむアイテムは、選択範囲全体@@@ の前後の文字列で囲います。
e.g.
・snippets.txt のアイテム:<pre>\n@@@\n</pre>
・選択範囲の文字列:
var d = editor.ActiveDocument;
var s = d.selection;
▼ 結果 ▼
<pre>
var d = editor.ActiveDocument;
var s = d.selection;
</pre>
  • @4つ@@@@)ふくむアイテムは、選択範囲の各行@@@@ の前後の文字列で囲います。
e.g.
・snippets.txt のアイテム:<code>@@@@</code>
・選択範囲の文字列:
var d = editor.ActiveDocument;
var s = d.selection;
▼ 結果 ▼
<code>var d = editor.ActiveDocument;</code>
<code>var s = d.selection;</code>
  • 設定項目 var cheese = "@"@ を任意の文字に変更可。

ダウンロード[編集]

>> 「ファイル:クリップボード履歴.zip」(アイコン入り)
最終更新: 2020-06-27
sunipetts.txt は「スニペットプラグイン」のものがあれば共用、なければ新規に生成(要 includeライブラリ)しますので、マクロ本体の JS ファイルとアイコンだけをご利用ください。
  • ポップアップメニュー内の連番アイテムの桁埋め(右寄せ/空白埋め)用の 空白文字 を設定する項目 var b1 = " "; を追加し、初期値を半角空白(U+0020 " ")にしました(※ これまで MS UI Gothic に最適化してあった桁埋め方法をカスタマイズできるようにした)。
・MS UI Gothic では2分アキ(EN SPACE) "\u2002"
・Meiryo UI では和字間隔(全角空白) "\u3000"
・Segoe UI では図形間隔(FIGURE SPACE) "\u2007" にすると具合がよいようです。

ソースコード[編集]

#title = "クリップボード履歴..."
#tooltip = "クリップボード履歴 と スニペット"
#include "include/IO.js"
#icon = "clipboard_history[1].ico"
// #icon = "Mery用 マテリアルデザインっぽいアイコン.icl",314

/**
 * --------------------------------------------------
 * 「クリップボード履歴 と スニペット」マクロ
 *  sukemaru, 2019-08-01 - 2020-06-27
 *  https://www.haijin-boys.com/wiki/「クリップボード履歴」メニューのマクロ化
 * --------------------------------------------------
 * 「クリップボード履歴」メニューと「スニペット」プラグインと同等の機能を
 *   ひとつのポップアップメニューに統合します。
 * 
 * ※ Ver 2.8.0 以前の Mery では「クリップボード履歴」機能を使用できません。
 * ※「スニペット」プラグインを導入していない場合でも
 *   「スニペット」機能(ピン止め)を使用できます。
 * 
 * ※「include ライブラリ」が必要です。
 *    https://www.haijin-boys.com/wiki/includeライブラリ
 * ※「GetKeyState.exe」で機能を拡張できます。
 *    https://www.haijin-boys.com/wiki/GetKeyState.exe(キー状態取得実行ファイル)
 */


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

// ■ 貼り付けたアイテムをクリップボード履歴の先頭に移動する
var toTop = false;          // true: 移動する / false: 移動しない

  // true にすると、Ctrl+V でつづけて貼り付け可
  // false なら、クリップボードのアイテムの順番を維持する

  // ■ toTop = true のとき
  //   「n 番目から m 番目までをつなげて貼り付け」したアイテムを
  //    クリップボード履歴の先頭に登録する(※ただし 16 番目だったアイテムが消える)
  var unitToTop = false;    // true: 登録する / false: 登録しない


// ■ 「任意の文字列でつなげて貼り付け」で入力した文字列の一時記憶方法
var tagType = 2;

  // 0 : 一時記憶なし
  // 1 : タブ(文書)ごとに一時記憶する(Document.Tag)
  // 2 : ウインドウごとに一時記憶する(Editor.Tag)
  // 3 : すべてのタブとウインドウ共通で一時記憶する(window.Tag)


// ■ "@@@" をふくむピン止めアイテムを "選択範囲の囲いこみ用" にする
var sandwich = false;       // true: する / false: しない
var cheese = "@"            // 初期値:"@"

  // sandwich = true なら、ピン止めアイテムが "@@@" をふくむときに...
  // ⇒ "@@@@" (cheese × 4) で区切られた前後の文字列で「選択範囲の各行」を囲う
  // ⇒ "@@@"  (cheese × 3) で区切られた前後の文字列で「選択範囲全体」を囲う
  // cheese = "@" の「@」は任意の文字に変更可


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

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

// ■ 半角英小文字や ascii 記号を全角で表示する
var toWideWidth = 0;        // (0 しない / 1 文字間を広げる / 2 全角にする)


// ■ 連番アイテムの桁埋め(右寄せ/空白埋め)用の空白文字の定義
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"
     にすると具合がよさそうですが、ウエイトによって幅が変わるので... */


// ■ GetKeyState.exe のフルパスを指定する場合( \ 記号はふたつがさね「\\」で)
// 未指定 "" なら、Mery インストールフォルダの Macros\GetKeyState.exe
var getKeyStatePath = "";   // ※ GetKeyState.exe なしのときも "" にする

  // ■ クリップボードのアイテムの Ctrl+クリックで
  //    ¥ を ¥¥ に置換して貼り付ける
  var ctrlEscape = false;

    // true なら、 Ctrl+クリックで ¥ を ¥¥ に置換して貼り付け
    // false なら、Ctrl+クリックで 貼り付けしてからアイテムを削除
    // GetKeyState.exe がない場合は無効

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


var start = new Date();

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

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 versionCheck = VersionCheck( "2.8.1" );
var d  = editor.ActiveDocument;
var s  = d.selection;
var st = s.Text;
var $status = Status;

var gksPath   = getKeyStatePath || meryDir + "Macros\\GetKeyState.exe";
var gksExists = Fso.FileExists( gksPath );
var $ctrl = false;
var $Ctrl = function() {
  return ( gksExists
  && WshShell.Run( '"' + gksPath + '" c', 0, true ) === 1 );
}

var p = "";
var tagKey = "ClipboardMenu";
var ham  = cheese ? cheese.replace( /[.*+?^=!:${}()|[\]\/\\]/g, "\\$&" ) : "";
sandwich = ham ? sandwich : false;

var b1 = ( ! blankChr || ( "" + blankChr ).length > 1 )
       ? " " : blankChr;
Object.prototype.PadSpace = function(len, chr) {
  var dgt = String(this);
  len = Number(len) || dgt.length;
  chr = chr ? chr.toString() : b1;
  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 snText = "",  snArray = [],  snCount = 0;
if ( snIsExist ) {
  // snippet.txt を読みこむ
  snText  = IO.LoadFromFile( snPath, "utf-8" ) || "";
  // 空白行とセパレータ用の "-" の行を除去した配列にする
  snArray = snText.split( "\n" );
  for ( var i = 0; i < snArray.length; i ++ ) {
    if ( /(?:^-\t|^\s*-?\s*$)/.test( snArray[i] ) ) {
      snArray.splice( i --, 1 );
    }
  }
}
snCount = snArray.length;

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


// ポップアップメニューの準備
var menu = CreatePopupMenu();
var grayFlag = d.ReadOnly ? meMenuGrayed : 0;
var t = ( snCount ) ? "▼ " : "";
var label,  id;


// 「ピン止めアイテム/スニペット」サブメニュー
var subMenu0 = CreatePopupMenu();
menu.AddPopup( t + "ピン止めアイテム/スニペット (&S) " + t, subMenu0 );
if ( st ) {
  subMenu0.Add( "選択範囲を登録 (&P)", 300 );
}
if ( snIsExist ) {
  subMenu0.Add( "スニペットを編集 (&E) ...", 400 );
}
if ( st || snIsExist ) {
  subMenu0.Add( "", 0, meMenuSeparator );
  subMenu0.Add( "キャンセル & ", 0 );
}
if ( snCount ) {
  subMenu0.Add( "", 0, meMenuSeparator );
}

// スニペットのアイテムをメニューに
if ( snText ) {
  var sn0, sn1, sn2, tab1, tab2;
  var subArray = [];            // サブメニュー: 10 階層までを想定
  for ( var i = 0; i < 10; i ++ ) {
    subArray.push( [] );
  }
  var subID, subID1, subID2;
  for ( var i = 0, j = 1, tab0 = 0; i < snCount; i ++, j ++ ) {
    try {
      sn1 = snArray[i];
      sn2 = snArray[j] || "";   // 次の有効文字列の行(空行やセパレータ行は除外)
      // 行頭のタブのケタ数(階層の深さ)
      tab1 = ( sn1.charAt( 0 ) == "\t" )
           ? sn1.search( /[^\t]/ ) : 0;
      tab2 = ( sn2.charAt( 0 ) == "\t" )
           ? sn2.search( /[^\t]/ ) : 0;
      // メニューに表示させるラベル部分とメニューID
      label = MenuKey( sn1.replace( /^\t+|\t+[^\t]*$/g, "" )
                     , j, numWidth, menuWidth, b1, true );
      id = j + 400;
      // メインメニューにサブメニュー項目(見出し)を追加
      if ( tab1 == 0 && tab1 + 1 == tab2 ) {
        subID = subArray[0].length;
        subArray[0].push( CreatePopupMenu() );
        if ( grayFlag ) {
          menu.Add( label + "\t\u25B6", id, grayFlag ); //「▶」(\u25B6)
        }
        else {
          menu.AddPopup( label, subArray[0][ subID ] );
        }
        subMenu0.Add( "", 0, meMenuSeparator );
        sn0 = false;
      }
      // メインメニュー直下にアイテムを追加
      else if ( tab1 == 0 && ( tab1 == tab2 || tab1 + 1 < tab2 ) ) {
        menu.Add( label, id, grayFlag );
        if ( tab1 < tab0 && sn0 ) {
          subMenu0.Add( "", 0, meMenuSeparator );
        }
        subMenu0 .Add( label, id, grayFlag );
        sn0 = true;
      }
      // サブメニュー内にサブメニュー項目(見出し)を追加
      else if ( tab1 > 0 && tab1 + 1 == tab2 ) {
        subID1 = subArray[ tab1 ].length;
        subID2 = subArray[ tab1 - 1 ].length - 1;
        subArray[ tab1 ].push( CreatePopupMenu() );
        subArray[ tab1 - 1 ][ subID2 ].AddPopup(
          label, subArray[ tab1 ][ subID1 ] );
        if ( sn0 ) {
          subMenu0.Add( "", 0, meMenuSeparator );
        }
        sn0 = false;
      }
      // サブメニュー内にアイテムを追加
      else if ( tab1 > 0 && ( tab1 >= tab2 || tab1 + 1 < tab2 ) ) {
        subID = subArray[ tab1 - 1 ].length - 1;
        subArray[ tab1 - 1 ][ subID ].Add( label, id, grayFlag );
        if ( tab1 < tab0 && sn0 ) {
          subMenu0.Add( "", 0, meMenuSeparator );
        }
        subMenu0.Add( label, id, grayFlag );
        sn0 = true;
      }
      tab0 = tab1;
    }
    catch( e ) { ;
      /**
       * snArray[i] (行_A) と次の有効文字列の行 snArray[j] (行_B) の
       * タブインデントを比較して、行_A よりも 行_B が二段以上深い場合、
       * 行_A をサブメニュー見出し項目にせず、通常アイテムとして扱います。
       * 行_B から始まるグループは、親になる項目が適切に処理されないことにより、
       * メインメニュー部分の階層構造に正しく反映されなくなります
       * (状況によっては部分的にエラー扱いになります)。
       * ただし「▼ ピン止めアイテム/スニペット ▼」配下の
       * ベタ置きのアイテムとしては(状況によっては不完全に)表示されます。
       */
    }
  }
}
menu.Add( "", 0, meMenuSeparator );


// クリップボード履歴のアイテムをメニューに
if ( cbCount ) {
  var subMenu1 = CreatePopupMenu(); // クリップボード履歴の一覧
  var subMenu2 = CreatePopupMenu(); // 履歴のアイテムをスニペットに登録
  var subMenu3 = CreatePopupMenu(); // 貼り付けしてからアイテムを削除
  var subMenu4 = CreatePopupMenu(); // 履歴からアイテムを削除
  menu.AddPopup( "▼ クリップボード履歴の一覧 (&H) ▼", subMenu1 );
  for ( var i = 0; i < cbCount; i ++ ) {
    id = i + 1;
    label = MenuKey( cbArray[i], id, numWidth, menuWidth, b1 );
    menu.Add( label, id, grayFlag );
    subMenu2. Add( label, id + 100, grayFlag );
    subMenu3. Add( label, id + 200 );
    subMenu4. Add( label, id + 300 );
  }
  // subMenu1.Add( "", 0, meMenuSeparator );
  subMenu1.AddPopup( "履歴のアイテムをスニペットに登録 (&P)", subMenu4 );
  subMenu1.AddPopup( "貼り付けしてからアイテムを削除 (&M)", subMenu2 );
  subMenu1.AddPopup( "履歴からアイテムを削除 (&D)", subMenu3 );
  subMenu1.Add( "", 0, meMenuSeparator );
  subMenu1.Add( "すべての履歴を削除 (&E)", 99 );
  subMenu1.Add( "", 0, meMenuSeparator );
  subMenu1.Add( "キャンセル & ", 0 );
}
// Windows のクリップボードデータ
else if ( cbData.length ) {
  label = MenuKey( cbData, 1, numWidth, menuWidth, b1 );
  menu.Add( "▼ クリップボード ▼", 0, meMenuGrayed );
  menu.Add( label, 17, grayFlag );
} 
else {
  menu.Add( "※ クリップボードにテキストデータはありません ※"
          , 0, meMenuGrayed );
}

if ( ! ctrlEscape && ( cbData0 || cbData ).indexOf( "\\" ) > -1 ) {
  menu.Add( "", 0, meMenuSeparator );
  menu.Add( "\u00A5 を \u00A5\u00A5 に置換して "    //「¥」(\u00A5)
          + "履歴の先頭アイテム を貼り付け (&E)"
          , 18, grayFlag );
}

if ( cbCount > 1 ) {
  var u1 = "履歴の n 番目から m 番目までを";
  var u2 = "つなげて貼り付け ";
  menu.Add( "", 0, meMenuSeparator );
  menu.Add( u1 + u2 + "(&M)", 94, grayFlag );
  menu.Add( u1 + " 任意の文字列 で" + u2 + "(&P)...", 95, grayFlag );
  // menu.Add( u1 + " 半角空白 で" + u2 + "(&W)", 96, grayFlag );
  menu.Add( u1 + " 改行 で" + u2 + "(&N)", 97, grayFlag );
  // menu.Add( u1 + " 空行 で" + u2 + "(&B)", 98, grayFlag );
}

  menu.Add( "", 0, meMenuSeparator );
  menu.Add( "追加コピー (&C)", 92 );
  menu.Add( "追加切り取り (&T)", 93, grayFlag );

if ( cbData || cbCount ) {
  menu.Add( "", 0, meMenuSeparator );
  menu.Add( "クリップボードのすべての履歴を削除する (&E)", 99 );
}

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


// ステータスバーの表示
Status = ( grayFlag )
       ? " ドキュメントは書き換え禁止です。"
       : ( ! versionCheck )
       ? " 「クリップボード履歴」機能の動作要件は"
         + " \"Mery Ver 2.8.1\" 以上です。"
       : ( ctrlEscape && gksExists && ( cbCount || cbData ) )
       ? " 履歴アイテムの Ctrl+クリック で"   //「¥」(\u00A5)
         + " \u00A5 を \u00A5\u00A5 に置換して貼り付けます。"
       : ( gksExists && cbCount )
       ? " 履歴アイテムの Ctrl+クリック で"
         + " 「貼り付けしてからアイテムを削除」"
       : ( gksExists && snCount )
       ? " ピン止めアイテムの Ctrl+クリック で"
         + " 「クリップボードにコピーして貼り付け」"
       : " 「クリップボード履歴 と スニペット」マクロ";
Status += " [ "
       + ( ( new Date() - start ) / 1000 ).toFixed( 3 ).replace( /\./, ". " )
       + " 秒 ]";


// ポップアップメニューを表示
var r = menu.Track( + menuPosMouse );


main: {
  if ( ! r ) {
    Status = $status;  break main;
  }
  editor.ExecuteCommandByID( MEID_WINDOW_ACTIVE_PANE = 2189 );


  // 94, 95, 96, 97, 98: n 番目から m 番目まで貼り付け
  unit:
  if ( r >= 94 && r <= 98 ) {
    $ctrl = $Ctrl();
    // 半角変換した ascii 文字列を返す関数
    var ToHalfWidth = function( strVal ) {
      return strVal.replace( /[!-~]/g, function( tmp ) {
          return String.fromCharCode( tmp.charCodeAt(0) - 0xFEE0 )
    } ) };

    // 入力ダイアログ×2回	※全角/半角の数字のみを有効文字列として取得する
    var p1 = + ToHalfWidth( Prompt( "さいしょのアイテムの番号", "" ) )
               .replace( /\D/g, "" );
    if ( p1 <= 0 || p1 > cbCount ) {
      Status = " キャンセル";  break unit;
    }
    var p2 = + ToHalfWidth( Prompt( "さいごのアイテムの番号", p1 ) )
               .replace( /\D/g, "" );
    if ( p2 <= 0 || p2 > cbCount ) {
      Status = " キャンセル";  break unit;
    }

    // 入力された番号が同一なら、通常の貼り付けコマンド r == 1~16 に飛ぶ
    if ( p1 === p2 ) {
      r = p1;  break unit;
    }

    // 上から順 または 下から順
    else {
      var str = "";
      var b = ( r === 95 ) ? AddStrPrompt( tagType, tagKey )
            : ( r === 96 ) ? " "
            : ( r === 97 ) ? "\n"
            : ( r === 98 ) ? "\n\n"
            :/* r ===94 */   "";
      if ( p1 < p2 ) {  // 上から順
        for ( var i = p1; i <= p2; i ++ ) {
          str += cbArray[ i - 1 ] + b;
        }
      }
      else {            // 下から順
        for ( var i = p2; i <= p1; i ++ ) {
          str = cbArray[ i - 1 ] + b + str;
        }
      }
      str = str.slice( 0, b ? - b.length : str.length );

      // 貼り付け	※ Ctrl キーを押しながらのときは、 ¥ を ¥¥ に置換
      s.Text = ( ctrlEscape && $ctrl )
             ? str.replace( /\\/g, "\\\\" ) : str;

      // まとめたデータを履歴の先頭に登録
      if ( toTop && unitToTop ) { cb.SetData( pStr ); }
    }
  }	// unit:{}


  // 1 ~ 17: クリップボード履歴のアイテムを貼り付け
  // ※ Ctrl キーを押しながらのときは、 ¥ を ¥¥ に置換
  if ( ctrlEscape && r > 0 && r <= 17 ) {
    $ctrl = $ctrl || $Ctrl();
    var str = ( r === 17 ) ? cbData : cbArray[ r -1 ];
    s.Text = $ctrl ? str.replace( /\\/g, "\\\\" ) : str;

    // 貼り付けたデータを履歴の先頭に移動
    if ( toTop && r <= 16 ) {
      cbArray.unshift( cbArray.splice( r - 1, 1 ) );
      for ( var i = 0; i < cbCount; i ++ ) {
        cb.SetData( cbArray[i], i );
      }
      cb.SetData( cbArray[0] );
    }
  }

  else if ( r > 0 && r <= 16 ) {
    s.Text = cbArray[ r - 1 ];

    // Ctrl キーを押しながらのときは、貼り付けしてからアイテムを削除
    if ( $Ctrl() ) {
      cb.ClearData( r - 1 );
      if ( r === 1 ) { cb.ClearData(); }
      Status = " クリップボード履歴からアイテムを削除しました。";
    }

    // 貼り付けたデータを履歴の先頭に移動
    else if ( toTop ) {
      cbArray.unshift( cbArray.splice( r - 1, 1 ) );
      for ( var i = 0; i < cbCount; i ++ ) {
        cb.SetData( cbArray[i], i );
      }
      cb.SetData( cbArray[0] );
    }
  }


  // Windows のクリップボードデータから貼り付け
  else if ( r === 17 ) {
    s.Text = cbData;
    // Ctrl キーを押しながらのときは、貼り付けしてからアイテムを削除
    if ( $Ctrl() ) {
      cb.ClearData();
      Status = " クリップボードからアイテムを削除しました。";
    }
  }


  // ¥ を ¥¥ に置換して 履歴の先頭アイテム を貼り付け
  else if ( r === 18 ) {
    s.Text = ( cbData0 || cbData ).replace( /\\/g, "\\\\" );
  }


  // 追加コピー
  else if ( r === 92 ) {
    var line = d.GetLine( s.GetActivePointY( mePosLogical ), 0 );
    var str = s.IsEmpty ? ( line ? line + "\n" : "" )
                        : s.Text;
    if ( str ) {
      var oldData = cbData0 || cbData || "";
      cb.ClearData( 0 );
      cb.SetData( oldData + str );
    }
  }

  // 追加切り取り
  else if ( r === 93 ) {
    if ( s.IsEmpty ) { s.SelectLine(); }
    var str = s.Text;
    if ( str ) {
      var oldData = cbData0 || cbData || "";
      cb.ClearData( 0 );
      cb.SetData( oldData + str );
    }
    s.Delete();
  }


  // 99: クリップボード履歴をすべて削除する
  else if ( r === 99
  && Confirm( "クリップボードのすべての履歴を削除しますか? " ) ) {
    for ( var i = cbCount - 1; i >= 0; i -- ) {
      cb.ClearData( i );
    }
    cb.ClearData();
    Status = " クリップボード履歴からすべてのアイテムを削除しました。";
  }


  // 101 ~ 116: 貼り付けしてからアイテムを削除
  else if ( r > 100 && r < 200 ) {
    s.Text = cbArray[ r - 101 ];
    cb.ClearData( r - 101 );
    Status = " 履歴からアイテムを削除しました。";
  }


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


  // 300 ~ 316: スニペットに登録(ピン止め)
  else if ( r >= 300 && r < 400 ) {
    try {
      // データフォルダに Plugins\\Snippets フォルダがなければフォルダを生成
      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 = ( r === 300 )
              ? st
              : ( cb.GetData( r - 301 ) || cb.GetData() );
      // snippets.txt の末尾に追加登録する
      str = snText
          + ( ( ! snText || snText.charAt( snText.length - 1 ) === "\n" )
            ? "" : "\n" )
          + str.replace( /[\\\t\n\r]/g, function( tmp ) {
              return ( tmp === "\\" ) ? "\\\\"
                   : ( tmp === "\t" ) ? "\\t"
                   : ( tmp === "\n" ) ? "\\n"
                   :/* tmp === "\r" */  "\\r";
            } );
      IO.SaveToFile( snPath, str, "utf-8", true );
      var copyFrom = ( r === 300 ) ? " 選択範囲" : " 選択したアイテム";
      Status = copyFrom + "をスニペットに登録しました。";
    }
    catch( e ) {
      Status = " スニペットに登録できませんでした。";
    }
  }


  // 400: スニペットを編集(snippets.txt を開く)
  else if ( r === 400 ) {
    WshShell.Run( "\"" + meryPath + "\" \"" + snPath + "\"" );
    Status = " " + snPath;
  }


  // 401 ~ : ピン止めアイテムを貼り付け
  else if ( r > 400 ) {
    var $ctrl = $Ctrl();
    var snStr = snArray[ r - 401 ].replace( /^(?:\t)*[^\t]*\t/, "" )
                                  .split( "\\\\" );
    for ( var i = 0, len = snStr.length; i < len; i ++ ) {
      snStr[i] = snStr[i].replace( /\\t|\\r|\\n|\\/g, function( tmp ) {
        return ( tmp === "\\t" ) ? "\t"
             : ( tmp === "\\r" ) ? "\r"
             : ( tmp === "\\n" ) ? "\n"
             :/* tmp === "\\" */   "";
      } );
    }
    snStr = snStr.join( "\\" );

    // ピン止めアイテムで選択範囲を囲う
    var ham3 = ham + ham + ham,  ham4 = ham3 + ham;
    var reg = /\n?$/,  n = st.match( reg );
    var str = st.replace( reg, "" );

    // "@@@@" で区切られた前後の文字列で選択範囲の各行を囲う
    if ( sandwich && snStr.indexOf( ham4 ) > -1 ) {
      var a = str.split( "\n" );
      for ( var i = 0, len = a.length; i < len; i ++ ) {
        a[i] = snStr.replace( RegExp( ham4, "g" ), a[i] );
      }
      // クリップボード経由で「貼り付け」
      cb.SetData( str = a.join( "\n" ) + n );
      s.Paste();
      // クリップボードデータを復帰
      cb.ClearData();
      for ( var i = 0; i < 16; i ++ ) {
        cb.SetData( cbArray[i] || "", i );
      }
      cb.SetData( cbArray[0] );
      if ( $ctrl ) { cb.SetData( str ); }
    }

    // "@@@" で区切られた前後の文字列で選択範囲全体を囲う
    else if ( sandwich && snStr.indexOf( ham3 ) > -1  ) {
      s.Text = str = snStr.replace( RegExp( ham3, "g" ), str ) + n;
      if ( $ctrl ) { cb.SetData( str ); }
    }

    // ピン止めアイテムを貼り付け
    else {
      s.Text = snStr;
      // Ctrl キーを押しながらのときは、貼り付けたアイテムをコピー
      if ( $ctrl ) {
        cb.SetData( snStr );
        Status = " クリップボードにアイテムをコピーしました。";
      }
    }
  }

}	// main:{}


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

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


/**
 * 関数 MenuKey( str, num, numWidth, menuWidth, blankChr, boolean )
 * ポップアップメニューに表示するラベルを生成する
 * ・行頭空白を除去、空白文字を圧縮:        → 「›」(U+203A)
 * ・改行記号を可視化:                      → 「↲」(U+21B2)
 * ・削られてしまう 「&」 を補完
 * ・「¥」(U+005C) を 「∖」(U+2216) に置換:     → 「╲」(U+2572)
 * ・ゼロ幅や特殊な空白文字を豆腐に置換:    → 「▯」(U+25AF)
 * ・判別しづらい半角記号を全角に置換:      !"%'(),.:;@[]`{|}
 *     + 「a-z」 を全角に置換
 *   または半角記号の前後にスキマをつける:  HAIR SPACE 「 」(U+200A)
 * ・行番号を空白でケタ埋め:
 * ・menuWidth を目安に文字数を切り詰め
 */
function MenuKey( str, num, numWidth, menuWidth, b1, conv ) {
  var keyWidth = menuWidth;
  if ( conv ) {
    var strArr = str.replace( /^[^\t]*\t/, "" ).split( "\\\\" );
    for ( var i = 0; i < strArr.length; i ++ ) {
      strArr[i] = strArr[i].replace( /\\t/g, " \u203A " )	//「›」
                           .replace( /\\r|(\\r)?\\n/g, " \u21B2 " )	//「↲」
                           .replace( /\\/g, "" );
    }
    str = strArr.join( "\\" );
  }
  var reg = /[\u00A0\u1680\u180E\u2000-\u200E\u2028\u2029\u202F\u205F\u2062\u2063\uFEFF]/g;
  var key = str.replace( /(?:\t|[ ]{3,}|[ ]{2,})+/g, " \u203A " )	//「›」
               .slice( 0, menuWidth *2)
               .replace( /(?:\r?\n|\r)/g, " \u21B2 " )  //「↲」
               .replace( /[&]/g, "&&" )
               .replace( /[\\]/g, "\u2216" )            //「∖」
               .replace( reg, "\u25AF" );               //「▯」
  if ( toWideWidth === 2 ) {
    key = key.replace( /[!"%'(),.:;@\[\]`a-z{|}]/g, function( tmp ) {
      return String.fromCharCode( tmp.charCodeAt( 0 ) + 0xFEE0 )
    } );
  }
  else if ( toWideWidth ) {
    // スラッシュ 以外の ascii 記号と fijl に HAIR SPACE (\u200A) を付加
    // [!"#$%'()*+,-.:;<=>?@\[\]^_`{|}~] + "&&"
    key = key.replace( /[!-%'-.:-@\[-`{-~fijl\u00A5\u22A0\u25AF]|&&/g
                     , "\u200A$&\u200A" )	//「¥」「⊠」「▯」
             .replace( /\u200A+/g, "\u200A" )
             .replace( RegExp( b1 + "\u200A", "g" ), b1 );
    // HAIR SPACE,「›」,「↲」
    keyWidth += ( key.match( /[\u200A\u203A\u21B2]|&&/g ) || "" ).length;
  }
  num = num.PadSpace( numWidth ).replace( /\d$/, "&$&:" + b1 );
  key = ( key.length > keyWidth )
      ? key.slice( 0, keyWidth ) + " ..." : key;
  return num + key;
}


/**
 * 関数 GetTag( tagType, tagKey, property )
 * 指定された Tag の値を返す
 */
function GetTag( tagType, tagKey, property ) {
  try {
    var obj = ( typeof tagType === "object" ) ? tagType
            : ( tagType === 1 ) ? editor.ActiveDocument
            : ( tagType === 2 ) ? editor
            : ( tagType === 3 ) ? window
            : window;
    return ( obj.Tag.Exists( tagKey )
    && ( property ? property in obj.Tag( tagKey ) : true ) )
    ? property
      ? obj.Tag( tagKey )[ property ]
      : obj.Tag( tagKey )
    : null;
  } catch( e ) { Status = e;  return null; }
}

/**
 * 関数 SetTag( value, tagType, tagKey, property )
 * 指定された値を Tag に書き込む
 */
function SetTag( value, tagType, tagKey, property ) {
  try {
    var obj = ( typeof tagType === "object" ) ? tagType
            : ( tagType === 1 ) ? editor.ActiveDocument
            : ( tagType === 2 ) ? editor
            : ( tagType === 3 ) ? window
            : window;
    if ( property ) {
      if ( obj.Tag.Exists( tagKey ) ) {
        obj.Tag( tagKey )[ property ] = value;
      }
      else { obj.Tag( tagKey ) = { property: value }; }
    }
    else { obj.Tag( tagKey ) = value; }
  }
  catch( e ) { Status = e; }
  finally { return; }
}


/**
 * 関数 AddStrPrompt( tagType, tagKey )
 * 「任意の文字列」コマンド
 * 入力ダイアログで指定された文字列を返す
 */
function AddStrPrompt( tagType, tagKey ) {
  // 前回使用した文字列があればダイアログの初期値に再利用
  var str = "";
  if ( tagType && ( t = GetTag( tagType, tagKey, "addStr" ) ) ) {
    str = t;
  }
  // 入力ダイアログ
  var msg = "連結用文字列:\t"
          + "改行 = \\\\\\n ; タブ = \\\\\\t  (注:¥記号3つ)";
  p = Prompt( msg, str ) || "";
  if ( p && tagType ) {
    SetTag( p, tagType, tagKey, "addStr" );
  }
  return p.replace( /\\\\\\t/g, "\t" ).replace( /\\\\\\n/g, "\n" );
}

変更履歴[編集]

• 2020-06-27
 ・GetKeyState.exe による機能拡張の追加
  (クリップボード履歴アイテムの Ctrl+クリックで ¥ を ¥¥ に置換して貼り付け)
 ・「追加コピー」「追加切り取り」コマンドを追加
 ・「n 番目から m 番目まで貼り付け」コマンドを追加
 ・「n 番目から m 番目まで貼り付け」したデータのまとまりを履歴の先頭に登録する設定項目を追加
 ・「ピン止めアイテムで選択範囲を囲う」機能と設定項目を追加
 ・連番数字のケタ埋め用の空白文字のカスタマイズ用設定項目を追加
 ・ソースコード内の「能書き (詳細説明)」を削除
 ・ソースコード内の unicode 文字をコードポイント \uHHHH に書き換え
• 2020-06-05
 ・「追加コピー」マクロの節を削除
 ⇒「追加コピー・追加切り取り」マクロ(※ Mery Ver 2.8.1 以降用)のページを新設
• 2020-05-02
 ・「n 番目から m 番目まで貼り付け」 を追加掲載(ダウンロードの ZIP 書庫のソースコードは変更していません)
 ・「追加コピー」マクロの節を追加(ZIP 書庫には未収録)
ここまでで、だいたいはスニペットの機能を再現できたとおもいます…。
• 2019-11-29
 ・「半角英文字を全角で表示」しない設定のとき、ascii 記号の前後に HAIR SPACE
 ・snippets.txt 内で、行頭が "「-」×1 + タブ文字" の行を無視
 ・クリップボード履歴から貼り付けたアイテムを、履歴の先頭に移動させるオプションを追加
 ・ピン止めアイテムを Ctrl+クリックした場合、アイテムを貼り付けし、そのアイテムをクリップボードにコピー
• 2019-11-09
 ・Ver 2.8.0 以前の Mery でもスニペット機能だけ利用できるように変更
 ・「クリップボード履歴の重複アイテム削除」の設定項目を廃止( "削除する" で固定)
 ・snippets.txt 内の単独「\」の処理をスニペットプラグインにあわせる修正
 ・snippets.txt 内のセパレータ用の「-」の行をスキップする処理を追加
• 2019-11-05
 ・各アイテムの行数表示を廃止
 ・クリップボード履歴からスニペットにアイテムを登録するコマンドを追加
 ・メインメニューに表示するピン止めアイテム数の上限設定を廃止
 ・スニペット機能での「ピン止め/貼り付け」のさいの、改行コードやタブコードではない文字列「\n」や「\t」の扱いを再修正
  ( … したつもりだが、まだ不完全かも)
 ・スニペットプラグインのサブメニュー化階層構造をある程度再現した
  ( … つもりだが、アレンジしたので同じ表示状態にならないことがある)
• 2019-11-01:
 ・ステータス表示を追加
 ・クリップボードにテキストデータがないときのメニュー構成を変更
 ・アイテムの行数(改行数+1)をメニュー内に追加表示
 ・メニュー内に表示する改行コードの矢印を「↲」(U+21B2) に変更
 ・スニペット機能での「ピン止め/貼り付け」のさいの、改行コードやタブコードではない文字列「\n」や「\t」の扱いを修正
 ・snippets.txt がないときのエラーを修正
• 2019-08-06:
 ・「ピン止めアイテムを貼り付け」コードの変数ミスを修正
 ・クリップボード履歴内の重複アイテムを削除する設定を追加
 ・ピン止めアイテムが少ないときはメインメニューにアイテムを表示
 ・snippets.txt 内の空行を無視
 ・クリップボード履歴に長大なデータがあるときの動作速度を少しだけ改善
• 2019-08-01: 初版
スポンサーリンク